Search code examples
pythonlxmlpython-docx

How do I Add Custom XML to An Element Using Python-Docx?


I've been trying to get text wrapping for my table which isn't implemented in python-docx.

enter image description here

So far so good, I've isolated that MS Word adds <w:tblpPr w:leftFromText="180" w:rightFromText="180" w:vertAnchor="text" w:tblpY="1"/> as a child to the table properties element so I just need to inject that into my table in python-docx.

enter image description here

I got real close. Because I was able to find the element class in the library to add a child to. However my problem lays in the w: namespace because it won't let me construct an Element tag with the ':' character in it. I tried two methods, both failed.

tblPrElement = Element('w:tblpPr ', {'w:leftFromText': '180', 'w:rightFromText': '180', 'w:vertAnchor': 'text', 'w:tblpY': '1' })

tblPrElement = parse_xml('<w:tblpPr w:leftFromText="180" w:rightFromText="180" w:vertAnchor="text" w:tblpY="1"/>')

If I try it omitting the w: ...

document = Document()
table = document.add_table(rows=1, cols=3)
tblPrElement = parse_xml('<tblpPr leftFromText="180" rightFromText="180" vertAnchor="text" tblpY="1"/>')
table._tblPr.append(tblPrElement)

...then the document builds but ms word can't open it because the xml then looks like this:

enter image description here


Solution

  • python-docx has a qn method for using with element creation.

    def qn(tag):
        """
        Stands for "qualified name", a utility function to turn a namespace
        prefixed tag name into a Clark-notation qualified tag name for lxml. For
        example, ``qn('p:cSld')`` returns ``'{http://schemas.../main}cSld'``.
        """
    

    It allows you to type XML with namespace prefixes.

    from docx.oxml.ns import qn
    
    def set_text_wrap_around_table(table):
        tbl_properties_element = Element(qn('w:tblpPr'),
                               {
                                  qn('w:leftFromText'): '180',
                                  qn('w:rightFromText'): '180',
                                  qn('w:vertAnchor'): 'text',
                                  qn('w:tblpY'): '1'
                               })
        table._tblPr.append(tbl_properties_element)
    

    If the element you'd like to create already has a class defined in python-docx you can create it using docx.oxml.OxmlElement for example, you can create a paragraph run element class like so:

    from docx.oxml import OxmlElement
    
    run_element = OxmlElement('w:r')
    

    This has the added benefits of having its possible children defined as attributes and more.