Replace text with URL in Word document using Open XML

Peter Karlström 216 Reputation points
2021-02-26T07:37:31.44+00:00

Hello
I have problems with a project where I want to replace som tagged text in a Word document with a clickable URL.
The sample code below uses a word document which contains the text [Webpage].
Here is the code with the problem:

Imports DocumentFormat.OpenXml
Imports DocumentFormat.OpenXml.Packaging

Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        MsgBox(processDocument("C:\temp\testdoc.docx", "[Webpage]", "Google", "https://www.google.com"), MsgBoxStyle.ApplicationModal + vbOKOnly, "Text replace test")

    End Sub

    Private Function processDocument(ByVal tDocFilename As String, ByVal tagText As String, ByVal replText As String, ByVal replURL As String) As String


        Using doc As WordprocessingDocument = WordprocessingDocument.Open(tDocFilename, True)
            Dim mainPart As DocumentFormat.OpenXml.Packaging.MainDocumentPart = doc.MainDocumentPart

            Dim textPLaceList As IEnumerable(Of Wordprocessing.Text) = mainPart.Document.Descendants(Of Wordprocessing.Text)()

            Try
                For Each textPlaceHolder As Wordprocessing.Text In textPLaceList
                    Dim parent = textPlaceHolder.Parent
                    If (TypeOf parent Is Wordprocessing.Run) Then
                        If textPlaceHolder.Text.Contains("[") And textPlaceHolder.Text.Contains("]") Then
                            Dim tmpHyperlink As New DocumentFormat.OpenXml.Wordprocessing.Hyperlink
                            tmpHyperlink.Anchor = replText
                            tmpHyperlink.DocLocation = replURL
                            tmpHyperlink.InsertBefore(Of Wordprocessing.Hyperlink)(tmpHyperlink, textPlaceHolder.Parent)
                            textPlaceHolder.Remove()
                            Exit For
                        End If
                    End If
                Next
                processDocument = "OK"
            Catch ex As Exception
                processDocument = "Could not replace text in document (" & ex.Message & ")"
            End Try

        End Using

    End Function

End Class

When I try to use InsertBefor or InsertAfter I get an error telling me that the "state" och the object is incorrect.
What does that mean?

Regards Peter Karlström

Office Development
Office Development
Office: A suite of Microsoft productivity software that supports common business tasks, including word processing, email, presentations, and data management and analysis.Development: The process of researching, productizing, and refining new or existing technologies.
3,479 questions
{count} votes

Accepted answer
  1. Peter Karlström 216 Reputation points
    2021-03-03T10:05:28.927+00:00

    After a tip from cheong00 I discovered I had approached this task in the wrong way.

    This is how it should be done:

    Imports DocumentFormat.OpenXml
    Imports DocumentFormat.OpenXml.Wordprocessing
    Imports DocumentFormat.OpenXml.Packaging
    
    Public Class Form1
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            MsgBox(processDocument("C:\temp\testdoc.docx", "[Webpage]", "Google", "https://www.google.com"), MsgBoxStyle.ApplicationModal + vbOKOnly, "Text replace test")
    
        End Sub
    
        Private Function processDocument(ByVal tDocFilename As String, ByVal tagText As String, ByVal replText As String, ByVal replURL As String) As String
    
            Using doc As WordprocessingDocument = WordprocessingDocument.Open(tDocFilename, True)
                Dim mainPart As DocumentFormat.OpenXml.Packaging.MainDocumentPart = doc.MainDocumentPart
    
                Dim textPLaceList As IEnumerable(Of Wordprocessing.Text) = mainPart.Document.Descendants(Of Wordprocessing.Text)()
    
                Try
                    For Each textPlaceHolder As Wordprocessing.Text In textPLaceList
                        Dim parent As Wordprocessing.Paragraph = textPlaceHolder.Parent.Parent
                        If (TypeOf parent Is Wordprocessing.Paragraph) Then
                            If textPlaceHolder.Text.Contains("[") And textPlaceHolder.Text.Contains("]") Then
                                Dim newParagraph As Paragraph = getURLParagraph(mainPart, replText, replURL)
                                parent.Parent.InsertBefore(Of Wordprocessing.Paragraph)(newParagraph, parent)
                                textPlaceHolder.Remove()
                                Exit For
                            End If
                        End If
                    Next
                    processDocument = "OK"
                Catch ex As Exception
                    processDocument = "Could not replace text in document (" & ex.Message & ")"
                End Try
    
            End Using
    
        End Function
    
        Private Function getURLParagraph(ByVal mainPart As MainDocumentPart, ByVal urlLabel As String, ByVal urlText As String) As Paragraph
    
            Dim urlExists As Boolean
            Dim hRelation As HyperlinkRelationship = Nothing
    
            Dim uri As System.Uri = New Uri(urlText)
    
            For Each hRel As HyperlinkRelationship In mainPart.HyperlinkRelationships
                If (hRel.Uri = uri) Then
                    urlExists = True
                    hRelation = hRel
                    Exit For
                End If
            Next
    
            Dim relationshipId As String
            If Not urlExists Then
                Dim rel As HyperlinkRelationship = mainPart.AddHyperlinkRelationship(uri, True)
                relationshipId = rel.Id
            Else
                relationshipId = hRelation.Id
            End If
    
            Dim newParagraph As Paragraph = New Paragraph(New Hyperlink(New ProofError() With {
            .Type = ProofingErrorValues.GrammarStart
        }, New Run(New RunProperties(New RunStyle() With {
            .Val = “Hyperlnk”}), New Text(urlLabel))) With {
            .History = OnOffValue.FromBoolean(True),
            .Id = relationshipId
        })
            Return newParagraph
    
        End Function
    End Class
    

    Hope this is of some help to others.

    Regards
    Peter Karlström

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Cheong00 3,471 Reputation points
    2021-03-02T08:03:51.167+00:00

    When textPlaceHolder.Parent is a Run, .InsertBefore/InsertAfter will fail. (Note that although I link to an issue page of OpenXML SDK, this is the limitation of Word format and I think you know too as I see your code is also trying to evade that)

    See if replacing textPlaceHolder.Parent in the second parameter with parent.Parent will help.