6

I want to set a style to a corrected word in MS Word text. Since it's not possible to change text style inside a run, I want to insert a new run with new style into the existing paragraph...

for p in document.paragraphs: 
   for run in p.runs: 
       if 'text' in run.text:      
            new_run= Run()
            new_run.text='some new text' 
            # insert this run into paragraph
            # smth like:
            p.insert(new_run) 

How to do it?

p.add_run() adds run to the end of paragraph, doesn't it?

Update

The best would be to be able to clone run (and insert it after a certain run). This way we reproduce the original run's style attributes in the new/cloned one.

Update 2

I could manage that insertion code:

if 'text' in run.text:
    new_run_element = CT_R() #._new() 
    run._element.addnext(new_run_element)
    new_run = Run(new_run_element, run._parent)
    ...

But after that:

  1. the paragraph runs number is left the same: len(p.runs)
  2. as I save that doc in file, the MS Word fails to open it: enter image description here
Igor Savinkin
  • 5,669
  • 8
  • 37
  • 69

1 Answers1

13

There's no API support for this, but it can be readily accomplished at the oxml/lxml level:

from docx.text.run import Run
from docx.oxml.text.run import CT_R
# ...
for run in p.runs:
    if 'text' in run.text:
        new_run_element = p._element._new_r()
        run._element.addnext(new_run_element)
        new_run = Run(new_run_element, run._parent)
        # ---do things with new_run, e.g.---
        new_run.text = 'Foobar'
        new_run.bold = True

If you want to insert the new run prior to the existing run, use run._element.addprevious(new_run_element). These two are methods on the lxml.etree._Element class which all python-docx elements subclass.
https://lxml.de/api/lxml.etree._Element-class.html

scanny
  • 26,423
  • 5
  • 54
  • 80
  • I've followed your code, but why when I run `r = CT_R.new()` I get the following error: `r = CT_R.new() AttributeError: type object 'CT_R' has no attribute 'new'` ? – Igor Savinkin Oct 10 '18 at 18:36
  • both in [CT_R class](https://github.com/python-openxml/python-docx/blob/master/docx/oxml/text/run.py) and [Run class](https://github.com/python-openxml/python-docx/blob/master/docx/text/run.py) I've not found any methods that you've using in that code... Also, see the **question Update**. – Igor Savinkin Oct 11 '18 at 11:38
  • please see **Update and Update 2**. – Igor Savinkin Oct 11 '18 at 12:09
  • Okay, apologies, I probably shouldn't try to do this from memory. There are a few approaches that can work you can replace that line with: `new_run_element = p._element._new_r()` and that should work. I'll update the code snippet. – scanny Oct 11 '18 at 20:44
  • thanks, it works. Where can I find docs on `._element._new_r()`, `._element.addnext()` methods and similar? – Igor Savinkin Oct 12 '18 at 10:05
  • `_Element.addnext()` is an `lxml` method you can find at the link at the bottom of the answer. `._new_r()` is a method added to the `CT_P` class by the metaclass underlying `BaseOxmlElement` (in `docx.oxml.xmlchemy` roughly [here](https://github.com/python-openxml/python-docx/blob/master/docx/oxml/xmlchemy.py#L286) as a result of the `r = ZeroOrMore('w:r')` child element declaration in `CT_P` [here](https://github.com/python-openxml/python-docx/blob/master/docx/oxml/text/paragraph.py#L16). This metaclass is how custom element classes are created customized to the particular element. – scanny Oct 12 '18 at 17:16
  • If you replace `p._element._new_r()` with `deepcopy(run.element)` you preserve the style of the previous run instead of clearing it. – toongeorges Feb 21 '20 at 01:03