Is there any way or method to combine runs of a single word which has been split into multiple runs in docx file

Viewed 291

I have a docx file with a certain style and structure and I want to replace some words of that docx file with some other word but I don't want to modify the format of the docx file.

For eg. If I have below mentioned text in docx file in certain font and size

This is used for testing

and I want to replace the word testing with checking. I should get

This is used for checking

With same font style and font size as the word testing

For this, I am trying to modify the xml file of the docx. While checking the xml file it is noticed that docx file divides paragraph text into multiple runs. As denoted below for the text This is used for testing the xml file gives

<w:document
    xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
    xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:o="urn:schemas-microsoft-com:office:office"
    xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
    xmlns:v="urn:schemas-microsoft-com:vml"
    xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
    xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
    xmlns:w10="urn:schemas-microsoft-com:office:word"
    xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
    xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
    xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex"
    xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
    xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
    xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
    xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se wp14">\n  
    <w:body>\n    
        <w:p w:rsidR="00084F90" w:rsidRPr="00B6714F" w:rsidRDefault="00084F90" w:rsidP="00084F90">\n      
            <w:pPr>\n        
                <w:rPr>\n          
                    <w:sz w:val="40"/>\n          
                    <w:szCs w:val="40"/>\n        
                </w:rPr>\n      
            </w:pPr>\n      
            <w:r>\n        
                <w:rPr>\n          
                    <w:b/>\n          
                    <w:bCs/>\n          
                    <w:sz w:val="40"/>\n          
                    <w:szCs w:val="40"/>\n        
                </w:rPr>\n        
                <w:t>This is a dock</w:t>\n      
            </w:r>\n      
            <w:r w:rsidRPr="00B6714F">\n        
                <w:rPr>\n          
                    <w:b/>\n          
                    <w:bCs/>\n          
                    <w:sz w:val="40"/>\n          
                    <w:szCs w:val="40"/>\n        
                </w:rPr>\n        
                <w:t xml:space="preserve"> document</w:t>\n      
            </w:r>\n      
            <w:r w:rsidRPr="00B6714F">\n        
                <w:rPr>\n          
                    <w:sz w:val="40"/>\n          
                    <w:szCs w:val="40"/>\n        
                </w:rPr>\n        
                <w:t>.</w:t>\n      
            </w:r>\n    
        </w:p>\n    
        <w:p w:rsidR="003F4CA3" w:rsidRDefault="00E55BE5" w:rsidP="00084F90">\n      
            <w:pPr>\n        
                <w:ind w:left="720" w:firstLine="720"/>\n      
            </w:pPr>\n      
            <w:r>\n        
                <w:rPr>\n          
                    <w:sz w:val="36"/>\n          
                    <w:szCs w:val="36"/>\n          
                    <w:u w:val="single"/>\n        
                </w:rPr>\n        
                <w:t xml:space="preserve">It is used </w:t>\n      
            </w:r>\n      
            <w:r w:rsidR="00084F90" w:rsidRPr="00B6714F">\n        
                <w:rPr>\n          
                    <w:sz w:val="36"/>\n          
                    <w:szCs w:val="36"/>\n          
                    <w:u w:val="single"/>\n        
                </w:rPr>\n        
                <w:t>for te</w:t>\n      
            </w:r>\n      
            <w:r w:rsidR="00084F90">\n        
                <w:rPr>\n          
                    <w:sz w:val="36"/>\n          
                    <w:szCs w:val="36"/>\n          
                    <w:u w:val="single"/>\n        
                </w:rPr>\n        
                <w:t>s</w:t>\n      
            </w:r>\n      
            <w:r w:rsidR="00084F90" w:rsidRPr="00B6714F">\n        
                <w:rPr>\n          
                    <w:sz w:val="36"/>\n          
                    <w:szCs w:val="36"/>\n          
                    <w:u w:val="single"/>\n        
                </w:rPr>\n        
                <w:t>ting.</w:t>\n      
            </w:r>\n      
            <w:bookmarkStart w:id="0" w:name="_GoBack"/>\n      
            <w:bookmarkEnd w:id="0"/>\n    
        </w:p>\n    
        <w:sectPr w:rsidR="003F4CA3">\n      
            <w:pgSz w:w="11906" w:h="16838"/>\n      
            <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>\n      
            <w:cols w:space="708"/>\n      
            <w:docGrid w:linePitch="360"/>\n    
        </w:sectPr>\n  
    </w:body>\n
</w:document>\n    

I want to replace the keys of the dictionary with values like testing with checking and it is getting split into three different runs can be seen in xml file. Is there any way to combine all the splits of the word testing or any general way to overcome this problem.

This is the python code

from lxml import etree
import xml.etree.ElementTree as ET
import zipfile
d = {"dock":"docx","testing":"checking"}

docx_filename = "Test.docx"

def get_word_xml(docx_filename):
   zip = zipfile.ZipFile(docx_filename)
   xml_content = zip.read('word/document.xml')
   return xml_content

def get_xml_tree(xml_string):
   return etree.fromstring(xml_string)


xml_string = get_word_xml(docx_filename)
xmltree = get_xml_tree(xml_string)
my_etree = etree.tostring(xmltree, pretty_print=True)

def _check_element_is(element, type_char):
     word_schema = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
     return element.tag == '{%s}%s' % (word_schema,type_char)
 
e = ET.ElementTree(xmltree)
for elt in e.iter():
    if _check_element_is(elt, 't'):
        print(elt.text)

The output i'm getting is

This is a dock

document

.

It is used

for te

s

ting.

What i want is

This is a dock document .

It is used for testing.

1 Answers

I think this kind of situation occurs when .docx is created from some converters or copying format from other formats to .docx

Following are the steps that provide a workaround for combining runs in a single paragraph and generating a new .docx file with well-formated paragraph fro easy the recess of text while parsing

Logic:

  1. Read each runs from within a same paragraph
  2. Combine a runs text in a string
  3. Detele the run once you read text from it
  4. add a new add_run method as a paragraph to get combine text from runs in a single paragraph.

Code:

from docx import Document

docx_file = "abc_test.docx"
document = Document(docx_file)   
paragraph = document.add_paragraph() 
  
for paragraph in document.paragraphs:
        print("In pragraph")   


    run_data="" 
    bold=False
    
    for run in paragraph.runs:
         #print("====",run.text)
         
         #Combine all together
         run_data+="".join(run.text)

         #if bold to keep same format
         if run.bold:
               bold=True 
         p = paragraph._p
         #print("style===>",p.style)
         #remove existing run
         p.remove(run._r)
            
    #print("combined run data===>",run_data)
    
    if bold:
        #add new paragraph run
        paragraph.add_run(run_data).bold = True          
    else:
        #add new paragraph run
        paragraph.add_run(run_data)  
document.save('converted_abc_test.docx')

converted_abc_test.docx is new combined runs in to paragraph document if you read this converted document again then you will see the issue is resolved.

#Note: I have checked with bold style only in the above example to preserve in a combined paragraph style from runs, you can do other formatting styles similarly also

Related