Search code examples
pythonpython-3.xpython-docx

how to create bookmarks in a word document, then create internal hyperlinks to the bookmark w/ python


I have written a script using python-docx to search word documents (by searching the runs) for reference numbers and technical key words, then create a table which summarizes the search results which is appended to the end of the word document.

some of the documents are 100+ pages, so I want to make it easier for the user by creating internal hyperlinks in the search result table, so it will bring you to the location in the document where the search result was detected.

once a reference run is found, I don't know how to mark it as a bookmark or how to create a hyperlink to that bookmark in the results table.

I was able to create bookmarks to external urls using the code in this page Adding an hyperlink in MSWord by using python-docx

I have also tried creating bookmarks, I found this page: https://github.com/python-openxml/python-docx/issues/109

the title relates to creating bookmarks, but the code seems to generate figures in word.

I feel like the two solutions can be put together, but I don't have enough understanding of xml/ word docs to be able to do it.

Update: I found some code that will add bookmarks to a word document, what is now needed is a way to link to this using a link in the word document https://github.com/python-openxml/python-docx/issues/403

*from docx import Document

def add_bookmark(paragraph, bookmark_text, bookmark_name):
    run = paragraph.add_run()
    tag = run._r  # for reference the following also works: tag =  document.element.xpath('//w:r')[-1]
    start = docx.oxml.shared.OxmlElement('w:bookmarkStart')
    start.set(docx.oxml.ns.qn('w:id'), '0')
    start.set(docx.oxml.ns.qn('w:name'), bookmark_name)
    tag.append(start)

    text = docx.oxml.OxmlElement('w:r')
    text.text = bookmark_text
    tag.append(text)

    end = docx.oxml.shared.OxmlElement('w:bookmarkEnd')
    end.set(docx.oxml.ns.qn('w:id'), '0')
    end.set(docx.oxml.ns.qn('w:name'), bookmark_name)
    tag.append(end)


doc = Document("test_input_1.docx")

# add a bookmakr to every paragraph
for paranum, paragraph in enumerate(doc.paragraphs):
    add_bookmark(paragraph=paragraph, bookmark_text=f"temp{paranum}", bookmark_name=f"temp{paranum+1}")
doc.save("output.docx")*

Solution

  • Solved: I got it from this post adding hyperlink to a bookmark

    this is the key line

    hyperlink.set(docx.oxml.shared.qn('w:anchor'), link_to,)
    

    As a bonus I have added in the ability to add a tool tip to your link:

    enjoy

    here is the answer:

    from docx import Document
    import docx
    from docx.enum.dml import MSO_THEME_COLOR_INDEX
    
    def add_bookmark(paragraph, bookmark_text, bookmark_name):
        run = paragraph.add_run()
        tag = run._r
        start = docx.oxml.shared.OxmlElement('w:bookmarkStart')
        start.set(docx.oxml.ns.qn('w:id'), '0')
        start.set(docx.oxml.ns.qn('w:name'), bookmark_name)
        tag.append(start)
    
        text = docx.oxml.OxmlElement('w:r')
        text.text = bookmark_text
        tag.append(text)
    
        end = docx.oxml.shared.OxmlElement('w:bookmarkEnd')
        end.set(docx.oxml.ns.qn('w:id'), '0')
        end.set(docx.oxml.ns.qn('w:name'), bookmark_name)
        tag.append(end)
    
    def add_link(paragraph, link_to, text, tool_tip=None):
        # create hyperlink node
        hyperlink = docx.oxml.shared.OxmlElement('w:hyperlink')
    
        # set attribute for link to bookmark
        hyperlink.set(docx.oxml.shared.qn('w:anchor'), link_to,)
    
        if tool_tip is not None:
            # set attribute for link to bookmark
            hyperlink.set(docx.oxml.shared.qn('w:tooltip'), tool_tip,)
    
        new_run = docx.oxml.shared.OxmlElement('w:r')
        rPr = docx.oxml.shared.OxmlElement('w:rPr')
        new_run.append(rPr)
        new_run.text = text
        hyperlink.append(new_run)
        r = paragraph.add_run()
        r._r.append(hyperlink)
        r.font.name = "Calibri"
        r.font.color.theme_color = MSO_THEME_COLOR_INDEX.HYPERLINK
        r.font.underline = True
    
    # test the functions
    if __name__ == "__main__":
    
        # input test document
        doc = Document(r"test_input_1.docx")
    
        # add a bookmark to every paragraph
        for paranum, paragraph in enumerate(doc.paragraphs):
            add_bookmark(paragraph=paragraph,
                         bookmark_text=f"{paranum}", bookmark_name=f"temp{paranum+1}")
    
        # add page to the end to put your link
        doc.add_page_break()
        paragraph = doc.add_paragraph("This is where the internal link will live")
    
        # add a link to the first paragraph
        add_link(paragraph=paragraph, link_to="temp0",
                 text="this is a link to ", tool_tip="your message here")
    
    
        doc.save(r"output.docx")