15

I am using python 2.7 with docx and I would like to change the background and text color of cells in my table based on condition.

I could not find any usefull resources about single cell formatting

Any suggestions?

Edit 1

my code

style_footer = "DarkList"
style_red = "ColorfulList"
style_yellow = "LightShading"
style_green = "MediumShading2-Accent6"
style_transperent = "TableNormal"
for a,rec in enumerate(data):
    #V headinh se piše prvo polje iz table heada
    document.add_heading(rec['tableHead'][0][0], level=1)
    image_path = imageFolder + "\\" + slike[a]
    document.add_picture(image_path, height=Inches(3.5))

    #y += 28
    #worksheet.insert_image( y, 1,imageFolder + "/" + slike[a])


    for i, head in enumerate(rec['tableHead']):
        table = document.add_table(rows=1, cols = len(head))
        hdr_cells = table.rows[0].cells
        for a in range(0,len(head)):
            hdr_cells[a].text = head[a] 


    for a,body in enumerate(rec['tableData']):
        row_cells = table.add_row().cells

        for a in range(0,len(body)):
            if body[a]['style'] == 'footer':
                stil = style_footer
            elif body[a]['style'] == 'red':
                stil = style_red

            elif body[a]['style'] == 'yellow':
                stil = style_yellow
            elif body[a]['style'] == 'green':
                stil = style_green

            else:
                stil = style_transperent

            row_cells[a].add_paragraph(body[a]['value'], stil)

document.save(wordDoc)

All cells are still the same.

Nikos Tavoularis
  • 2,843
  • 1
  • 30
  • 27
Yebach
  • 1,661
  • 8
  • 31
  • 58

10 Answers10

31

If you want to color fill a specific cell in a table you can use the code below. For example let's say you need to fill the first cell in the first row of your table with the RGB color 1F5C8B:

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)

Now if you want to also fill the second cell in the first row with the same color, you should create a new element otherwise if you use the same element as above the fill will move on and will disappear from the first cell...

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

...and so on for other cells.

Source: https://groups.google.com/forum/#!topic/python-docx/-c3OrRHA3qo

Nikos Tavoularis
  • 2,843
  • 1
  • 30
  • 27
6

With Nikos Tavoularis' solution, we have to create a new element for every cell. I have created a function that achieves this. Works in Python revision 3.5.6 and python-docx revision 0.8.10

from docx.oxml import OxmlElement
from docx.oxml.ns import qn

def set_table_header_bg_color(table.rows[row_ix].cell):
    """
    set background shading for Header Rows
    """
    tblCell = cell._tc
    tblCellProperties = tblCell.get_or_add_tcPr()
    clShading = OxmlElement('w:shd')
    clShading.set(qn('w:fill'), "00519E") #Hex of Dark Blue Shade {R:0x00, G:0x51, B:0x9E}
    tblCellProperties.append(clShading)
    return cell
"""
End of set_table_header_bg_color Function
"""
# main function
"""
..
..
..
1. Load Document
..
2. Access the required section
..
3. Load the required Table
..
4. Traverse to the cell by accessing the rows object
..
"""
for each_row in table.rows :
    for each_cell in each_row.cells:
        if each_cell.value satisfies a condition:
            set_table_header_bg_color(each_cell)
"""
5. Continue execution
"""
Rob2n
  • 3
  • 2
tmmsagheer
  • 71
  • 1
  • 5
4

What we found is that, if you do cell.add_paragraph('sometext', style_object), it will keep the existing empty paragraph and add an additional paragraph with the style, which is not ideal.

What you will want to do is something like:

# replace the entire content of cell with new text paragraph
cell.text = 'some text'
# assign new style to the first paragraph
cell.paragraphs[0].style = style_object 

Note that the style is applied to the paragraph not the cell, which isn't ideal for background colors (since it won't fill the enter cell if you have some padding. I haven't found a way around that (except in the case where you want EVERY cell to have a background color, you can apply a style to table.style).

Also, make sure that your styles are defined. You can check

styles = documents.styles
for s in styles:
  print s.name

to see all the styles you have. You can define new styles and also load a template document with pre-defined styles already.

Apollo Data
  • 1,267
  • 11
  • 20
3

It looks like instead of using the cell.text = "Something" method you need to use the cell.add_paragraph("SomeText", a_style) with a defined style - probably one of:

  • ColorfulGrid
  • ColorfulGrid-Accent1
  • ColorfulGrid-Accent2
  • ColorfulGrid-Accent3
  • ColorfulGrid-Accent4
  • ColorfulGrid-Accent5
  • ColorfulGrid-Accent6

Full list here.

If you use the “default” template document - otherwise you will have to create your own.

Steve Barnes
  • 27,618
  • 6
  • 63
  • 73
  • Is it possible to insert excel object into word document ? – Yebach Nov 05 '14 at 13:54
  • Not currently, you could insert a link to, or even into, an xlsx file or you could parse the xlxs file content into a table by using xlrd to get the data. – Steve Barnes Nov 05 '14 at 16:59
  • but still would the cells be styled? – Yebach Nov 05 '14 at 20:38
  • Only if YOU styled them as you created them in the _document_! – Steve Barnes Nov 05 '14 at 21:10
  • Which library should I use. docx, win32. I have them styled in excel. I just need to copy them to word. What is the easiest/fastest solution. Thank you – Yebach Nov 06 '14 at 07:31
  • xlrd works well for me but you will have to manually style them in the word document! The only other way would be to use win32 calls to get the cells from excel and paste into word but that relies on both being available on the executing machine. – Steve Barnes Nov 06 '14 at 07:39
  • If I try with manual styling like you suggested in your first post. Cells do not change. They are all the same. Any suggestions on why? I am having problems with installing pywin32 – Yebach Nov 06 '14 at 07:50
1

Taking from Nikos Tavoularis answer I would just change the shading_elm_1 declaration, as if you include the cell color in a loop for instance things might get messy.

As such, my suggestion would be:

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

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

I made a video demonstrating a way to do it here I took inspiration from the people above but I still had issues so I made this too help others.

https://www.youtube.com/watch?v=1Mgb95yigkk&list=PL_W7lgC2xeJfWBUllp7ALKOM5GUBMCVoP

    from docx import Document
    from docx.oxml import OxmlElement
    from docx.oxml.ns import qn
    document = Document("youfile.docx")
    Table = document.tables[0]

    #GET CELLS XML ELEMENT
    cell_xml_element = Table.rows[1].cells[0]._tc
    #RETRIEVE THE TABLE CELL PROPERTIES
    table_cell_properties = cell_xml_element.get_or_add_tcPr()
    #CREATE SHADING OBJECT
    shade_obj = OxmlElement('w:shd')
    #SET THE SHADING OBJECT
    shade_obj.set(qn('w:fill'), "ff00ff")
    #APPEND THE PROPERTIES TO THE TABLE CELL PROPERTIES
    table_cell_properties.append(shade_obj)

    document.save("yoursavefile.docx")

The code above will change the first cellof the second row of the first table in the document.Example of the output.

0

If you want to loop through the cells in a row use:

def color_row(row=0):
    'make row of cells background colored, defaults to column header row'
    row = t.rows[row]
    for cell in row.cells:
        shading_elm_2 = parse_xml(r'<w:shd {} w:fill="1F5C8B"/>'.format(nsdecls('w')))
        cell._tc.get_or_add_tcPr().append(shading_elm_2)

run the function to color cells in the second row

color_row(2)

Deena
  • 71
  • 1
  • 4
0

If you want to change the text color too, you can set it on the runs within the cell. I wrote this function to handle the cell background and text colors together (using Nikos' method for the fill):

def shade_cell(cell, fill=None, color=None):

    if fill:
        shading_elm = parse_xml(r'<w:shd {} w:fill="{}"/>'.format(nsdecls('w'), fill))
        cell._tc.get_or_add_tcPr().append(shading_elm)

    if color:
        for p in cell.paragraphs:
            for r in p.runs:
                r.font.color.rgb = RGBColor.from_string(color)

I originally tried to expand Nikos' solution by adding w:color="XXXXXX" to the w:shd tag but that didn't work for me. However setting the font color on each run got the result I wanted.

dashingdove
  • 304
  • 1
  • 4
  • 15
0

I have compiled the previous answers and added some features. Feel free to test: Create new file run the "main" part at the bottom.

""" adder for python-docx in order to change text style in tables:
font color, italic, bold
cell background color
based on answers on
https://stackoverflow.com/questions/26752856/python-docx-set-table-cell-background-and-text-color
"""
import docx # import python-docx (in order to create .docx report file)
from docx.oxml.ns import nsdecls
from docx.oxml import parse_xml

def change_table_cell(cell, background_color=None, font_color=None, font_size=None, bold=None, italic=None):
    """ changes the background_color or font_color or font style (bold, italic) of this cell.
    Leave the params as 'None' if you do not want to change them.
    params:
        cell: the cell to manipulate
        background_color: name for the color, e.g. "red" or "ff0000"
        font_color:
        font_size: size in pt (e.g. 10)
        bold:   requested font style. True or False, or None if it shall remain unchanged
        italic: requested font style. True or False, or None if it shall remain unchanged
    background_color: the color of cells background"""
    if background_color:
        shading_elm = parse_xml(r'<w:shd {} w:fill="{}"/>'.format(nsdecls('w'), background_color))
        cell._tc.get_or_add_tcPr().append(shading_elm)

    if font_color:
        for p in cell.paragraphs:
            for r in p.runs:
                r.font.color.rgb = docx.shared.RGBColor.from_string(font_color)

    if font_size:
        for p in cell.paragraphs:
            for r in p.runs:
                r.font.size = docx.shared.Pt(font_size)

    if bold is not None:
        for p in cell.paragraphs:
            for r in p.runs:
                r.bold = bold

    if italic is not None:
        for p in cell.paragraphs:
            for r in p.runs:
                r.italic = italic

def change_table_row(table_row, background_color=None, font_color=None, font_size=None, bold=None, italic=None):
    for cell in table_row.cells:
        change_table_cell(cell, background_color=background_color, font_color=font_color, font_size=font_size,
                          bold=bold,
                          italic=italic)

if __name__ == "__main__":  # do the following  code only if we run the file itself
    #document = docx.Document('template.docx') # create an instance of a word document, use the style that we have defined in 'template.docx'
    document = docx.Document()
    num_rows = 4
    num_cols = 3
    table = document.add_table(rows=num_rows, cols=num_cols)  # create empty table
    #table.style = document.styles['MyTableStyleBlue']  # test overwriting the predefined style
    # fill table
    for row in range(num_rows):
        for col in range(num_cols):
            table.rows[row].cells[col].text = f'row/col=({row},{col})'

    """ change color (see https://stackoverflow.com/questions/26752856/python-docx-set-table-cell-background-and-text-color) """
    # Nikos Tavoularis answered Apr 18, 2017 at 8:38
    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)

    # test new function derived from dyoung's answere of May 25 at 7:34, 2022
    change_table_cell(table.rows[0].cells[0], background_color=None, font_color="ff0000", bold=False)
    change_table_cell(table.rows[1].cells[2], background_color="00ff00", font_color="ff0000", font_size=20, bold=True)
    change_table_row(table.rows[3], background_color="lightgreen", font_color="0000ff", italic=True)  # https://www.delftstack.com/howto/python/colors-in-python/

    document.save('table_shading_test.docx')
papa
  • 11
  • 3
0

If you want to change the background of an entire column, here is a function for that. Pass the number of rows and table column to this function as arguments. Loop through the number of rows and change background color for each cell.

# to change background colour of the column cells
def change_columns_background(row_count, *columns):
    for i in range(1, row_count + 1):
        shading_elm_1 = parse_xml(
            r'<w:shd {} w:fill="BDD6EE"/>'.format(nsdecls('w')))
        columns[0].cells[i]._tc.get_or_add_tcPr().append(shading_elm_1)

Example function call:

# change the background color of first column for a table
        change_columns_background(row_count, my_table.columns[0])
rjose
  • 557
  • 5
  • 13