2

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

3
  • I provide an example of inserting custom XML into a word doc using python-docx here: stackoverflow.com/questions/70946715/… Commented Feb 5, 2022 at 0:34
  • @ChrisSears nice! So another solution is adding xml that declares the namespace at the start? Commented Feb 5, 2022 at 15:36
  • Correct. I just created a dummy parent element that declared the namespace and then grabbed the child of that parent element. In my case I was working with the OMML namespace for math formulas. Commented Feb 5, 2022 at 15:43

3 Answers 3

3

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.

Sign up to request clarification or add additional context in comments.

Comments

1

If you want to use lxml to append the XML using an Element you will have to provide python-docx's namespace map (docx.oxml.nsmap) when creating the Element.

The syntax for inserting a namespace in a tag name is {namespacevalue}tagname.

For example, <{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tblpPr/>

Here is a method you can use to set the table to text wrapping using your XML.

def set_text_wrap_around_table(table):
    w_namespace = '{' + nsmap['w'] + '}'
    tblPrElement = Element('{w}tblpPr'.format(w=w_namespace),
                           {'{w}leftFromText'.format(w=w_namespace): '180',
                            '{w}rightFromText'.format(w=w_namespace): '180',
                            '{w}vertAnchor'.format(w=w_namespace): 'text',
                            '{w}tblpY'.format(w=w_namespace): '1'},
                           nsmap)
    table._tblPr.append(tblPrElement)

Comments

1

Consider lxml's QName() for a functional version to avoid string formatting:

from lxml import etree
...


def set_text_wrap_around_table(table): 
     tblPrElement = etree.Element(
         etree.QName(nsmap["w"], "tblpPr"),
         {
              etree.QName(nsmap["w"], "leftFromText"): '180', 
              etree.QName(nsmap["w"], "rightFromText"): '180', 
              etree.QName(nsmap["w"], "vertAnchor"): 'text', 
              etree.QName(nsmap["w"], "tblpY"): '1'
         },
         nsmap
     )

     table._tblPr.append(tblPrElement)

1 Comment

Even w = nsmap["w"] then QName(w, "tblpPr") much cleaner! Parfait! Merci beaucoup.

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.