Search code examples
pythonxmldocxpython-docx

how to use python-docx to overwrite the xml contents of a cell


For editing .docx files in python, python-docx exposes some properties but not others. Text properties like bold, italics etc can easily be set, but cell properties like background shading and borders can't AFAIK.

What you can do in python-docx is to view the xml of the cell using the .xml attribute. So cell._tc.xml reveals that cell properties are held in a <w:tcPr> ... </w:tcPr> block. This can be a nested structure, for example

  <w:tcPr>
    <w:tcW w:w="1139" w:type="dxa"/>
    <w:tcBorders>
      <w:left w:val="single" w:sz="4" w:space="0" w:color="000000"/>
      <w:bottom w:val="single" w:sz="4" w:space="0" w:color="000000"/>
    </w:tcBorders>
    <w:shd w:fill="FFDBB6" w:val="clear"/>
  </w:tcPr>

I would like to copy the exact cell properties from one cell to another cell. I've tried:

  1. simply copying the xml <w:tcPr> block from one cell to the other. However, the .xml attribute is read only - python-docx doesn't allow setting xml directly.
  2. looping through the source cell's xml structure recursively and setting those properties in the destination cell. So something on the lines of
for child in source_cell._tc.get_or_add_tcPr().iterchildren():
    dest_cell._tc.get_or_add_tcPr().insert(child)

but that is raising TypeError: Argument must be bytes or unicode, got 'CT_TcPr'.

How can I achieve this?


Solution

  • Here is a function which copies the properties from one cell to another:

    def copy_cell_properties(source_cell, dest_cell):
        '''Copies cell properties from source cell to destination cell.
    
        Copies cell background shading, borders etc. in a python-docx Document.
    
        Args:
            source_cell (docx.table._Cell): the source cell with desired formatting
            dest_cell (docx.table._Cell): the destination cell to which to apply formatting
        '''
        # get properties from source cell
        # (tcPr = table cell properties)
        cell_properties = source_cell._tc.get_or_add_tcPr()
        
        # remove any table cell properties from destination cell
        # (otherwise, we end up with two <w:tcPr> ... </w:tcPr> blocks)
        dest_cell._tc.remove(dest_cell._tc.get_or_add_tcPr())
    
        # make a shallow copy of the source cell properties
        # (otherwise, properties get *moved* from source to destination)
        cell_properties = copy.copy(cell_properties)
    
        # append the copy of properties from the source cell, to the destination cell
        dest_cell._tc.append(cell_properties)
    

    There is a related discussion here - https://github.com/python-openxml/python-docx/issues/205