How to find text between strings in part of a document and create bookmark of that text

I am trying to develop a macro that searches a portion of a Word document (defined by a bookmark) for any text between two strings and create a bookmark of that text for each occurrence (the code adds a prefix letter because the end notes are numbers). This is specifically for a document downloaded from the Internet that contains end notes, so there is a bracketed reference in the text and before each end note (which is why I want the macro to address only one section -- the end notes -- of the document).

The code I have now finds all such bracketed text and in fact will shade it if I include a shading statement, but my line to add a bookmark only creates one bookmark with the prefix defined in the code, and does so only for the last occurrence of the search. Cam someone help me here? Thanks.

Sub Shade_Between_Chars_Bkmk()
Dim firstTerm As String
Dim secondTerm As String
Dim myRange As Range
Dim selRange As Range
Dim uBookmark As String
Dim strBookMarkPrefix

uBookmark = "lastsection"
strBookMarkPrefix = "A"

Set myRange = ActiveDocument.Bookmarks(uBookmark).Range
firstTerm = InputBox("Enter first term to find:")
secondTerm = InputBox("Enter second term to find:")
Dim blnSearchAgain As Boolean

Do
With myRange.Find
.Text = firstTerm
.MatchWholeWord = True
.Execute
myRange.Collapse direction:=wdCollapseEnd

If myRange.Find.Found Then

Set selRange = ActiveDocument.Bookmarks(uBookmark).Range
selRange.Start = myRange.End
.Text = secondTerm
.MatchWholeWord = True
.Execute
myRange.Collapse direction:=wdCollapseStart
selRange.End = myRange.Start
blnSearchAgain = True
ActiveDocument.Bookmarks.Add strBookMarkPrefix & selRange.Text, selRange

'selRange.Shading.BackgroundPatternColor = RGB(255, 150, 255) 'applies shading highlight
Else
    blnSearchAgain = False
End If
            
End With
Loop While blnSearchAgain
End Sub

Open in new window

marrick13Asked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

als315Commented:
Can you upload sample document?
GrahamSkanRetiredCommented:
You don't appear to be changing the prefix. Every time that you add a bookmark of the same name as an existing one, that one will be deleted, so you end up with only the last one.
Add this code at line 35
       
strBookMarkPrefix = Chr(Asc(strBookMarkPrefix) + 1)

Open in new window

marrick13Author Commented:
Thanks so much, Graham. I didn't make it clear that I didn't want to increment the prefix because the end notes are all numbers (1-54), and I only wanted a prefix because bookmarks cannot begin with a number (besides, incrementing a letter will error out after 26, no?). I did add your suggested line to #35 and got a bookmark error 5828. I commented it out and when I run as is, the macro creates bookmarks beginning with "A" and followed by the endnote number, but errors when  selRange.Text picks up a colon on p. 18 and stops after end note 16 because it can't create such a bookmark (why the letter prefix doesn't get inserted first, I don't know). I could add an "on error resume next" but would rather have the macro look only for numbers between the brackets. I think I would need a regex statement for that but don't know how to write it or where it should go.

I've attached a test document if that's easier to analyze.
Learn SQL Server Core 2016

This course will introduce you to SQL Server Core 2016, as well as teach you about SSMS, data tools, installation, server configuration, using Management Studio, and writing and executing queries.

GrahamSkanRetiredCommented:
Your attachment attempt doesn't seem to have worked
marrick13Author Commented:
I think that's because I saved it as a docm file (>). I tried this time as a .doc; pls let me know if that works.
Insert-Bookmark-Test--Dec-15A-2017-.doc
GrahamSkanRetiredCommented:
What text should be entered in the InputBoxes?
marrick13Author Commented:
Left bracket [ in first and right bracket [] in second. The macro will find the end note numbers between them (54 in all)
GrahamSkanRetiredCommented:
Is this what you mean?
firstTerm = "["
secondTerm = "]"
marrick13Author Commented:
Yes.
GrahamSkanRetiredCommented:
You have to make sure that the bookmark names are legal and unique (letters, numbers and underscores). The name length must be less than 40 characters. Note that it is possible in VBA to create illegal names without an error being raised at that time. With your code all of your names would all be illegal.

I have used wildcarding to identify the bracketed entry number as a single unit.

Sub Bookmark_Between_Chars_Bkmk2()
    Dim myRange As Range
    Dim selRange As Range
    Dim uBookmark As String
    Dim blnSearchAgain As Boolean
    
    uBookmark = "lastsection"
    
    Set myRange = ActiveDocument.Bookmarks(uBookmark).Range 'instantiate ranges
    Set selRange = ActiveDocument.Bookmarks(uBookmark).Range
    
    'find start of first entry
    With myRange.Find
        .Text = "\[[0-9]{1,}\]"
        .MatchWildcards = True
        If .Execute() Then
            selRange.Start = myRange.End
            myRange.Collapse direction:=wdCollapseEnd
            myRange.End = ActiveDocument.Bookmarks(uBookmark).Range.End
        Else
            MsgBox "No entries found"
            End
        End If
    End With
    
    'find end of current entry and start of next
    blnSearchAgain = True
    Do
        DoEvents
        selRange.Start = myRange.Start
        With myRange.Find
            .Text = "\[[0-9]{1,}\]"
            .MatchWildcards = True
            If .Execute() Then
            Else
                blnSearchAgain = False
            End If
            If blnSearchAgain Then
                selRange.End = myRange.Start
                myRange.Collapse direction:=wdCollapseEnd
                myRange.End = ActiveDocument.Bookmarks(uBookmark).Range.End
            Else
                selRange.End = ActiveDocument.Bookmarks(uBookmark).Range.End
                ActiveDocument.Bookmarks.Add GetNewBookmarkName(selRange.Text), selRange
            End If
        End With
    Loop While blnSearchAgain
End Sub

Function GetNewBookmarkName(strInText As String) As String
    Dim i As Integer
    Dim strText As String
    
    'valid characters only
    i = 1
    Do Until Len(strText) >= 20
        DoEvents
        Select Case Mid(strInText, i, 1)
            Case "A" To "Z", "a" To "z"
                strText = strText & Mid(strInText, i, 1)
            Case "_", 0 To 9
                If Len(strText) > 1 Then 'avoid first character of name
                    strText = strText & Mid(strInText, i, 1)
                End If
        End Select
        i = i + 1
        If i > Len(strInText) Then
            Exit Do
        End If
    Loop
        
    'ensure new bookmark name is unique
    Do While ActiveDocument.Bookmarks.Exists(strText)
        DoEvents
        strText = strText & i
        i = i + 1
    Loop
    GetNewBookmarkName = strText
End Function

Open in new window

marrick13Author Commented:
Thanks for the rewrite, Graham, but it is not quite what I was looking for. I wanted the macro to create bookmarks for each end note (in this doc all end notes are between brackets '[' and ']' with few exceptions) and to use the end note number as its unique name, prefixed by "A" or another letter. The idea is that other sections may be added with their own end notes numbers which would also start at 1, so the prefix would then make the end note bookmarks unique. So if one group of end notes for one block of text is numbered 1-54, its bookmarks should be A1, A2, A3, etc., and if another section is added, I would make its prefix 'B' and then the macro would bookmark only that section's end notes as B1, B2, B3, etc. I am assuming the end notes would always be numbers 1-whatever number, but of course, it is good to validate them.

When I ran your code, it created ONE bookmark named 'PragerUniversityPrag', and made the end note [54] and its text the bookmark. This end note starts with 'PragerUniversityPrag'. I've attached the text file with your code in it. I also created two bookmarks to denote the sections: sectionA is the block of text with end note references, and sectionAA is the end note block for that section. If other blocks are added, I would make one 'sectionB' and its end notes 'sectionBB' and want the macro to create bookmarks only for sectionBB. This way, I could create unique bookmarks for however many sections I need to add, running the macro each time I add a section and controlling which sections it bookmarks by the 'sectionAA', 'sectionBB', or sectionCC bookmarks.
GrahamSkanRetiredCommented:
In my tests, one bookmark was created for each entry, with the last one named as you say.
I now think I understand better what you want, so I''l try again.
GrahamSkanRetiredCommented:
See if this is any closer:
Sub Bookmark_Between_Chars_Bkmk3()
    Dim myRange As Range
    Dim rngEntry As Range
    Dim rngNextEntry As Range
    Dim uBookmark As String
    Dim blnSearchAgain As Boolean
    Dim strEntryNo As String
    Dim strNextEntryNo As String
    Dim strBookMarkPrefix As String
    
    strBookMarkPrefix = "A"
    uBookmark = "lastsection"
    
    Set myRange = ActiveDocument.Bookmarks(uBookmark).Range 'instantiate ranges
    Set rngEntry = ActiveDocument.Bookmarks(uBookmark).Range
    Set rngNextEntry = ActiveDocument.Bookmarks(uBookmark).Range
    
    With myRange.Find
        .Text = "\[[0-9]{1,}\]"
        .MatchWildcards = True
        Do While .Execute()
            rngEntry.End = myRange.Start
            strNextEntryNo = Replace(Replace(myRange.Text, "[", ""), "]", "")
            myRange.Collapse direction:=wdCollapseEnd
            rngNextEntry.Start = myRange.End
            myRange.End = ActiveDocument.Bookmarks(uBookmark).Range.End
            If Len(strEntryNo) > 0 Then
                ActiveDocument.Bookmarks.Add strBookMarkPrefix & strEntryNo, rngEntry
                rngEntry.Start = rngNextEntry.Start
            End If
            strEntryNo = strNextEntryNo
        Loop
        rngEntry.End = ActiveDocument.Bookmarks(uBookmark).Range.End
        ActiveDocument.Bookmarks.Add strBookMarkPrefix & strNextEntryNo, rngEntry

    End With
 End Sub

Open in new window

marrick13Author Commented:
Yes--much closer -- thank you! The only issue is that I need the end note bookmark to consist only of the prefix and the end note number; you have that as the name (which is fine), but the scope of each bookmark is the entire end note text. The reason I want only the end note number and prefix as the entire bookmark is that I want to create cross references to each end note via its bookmark. Each end note has the bracketed number as its reference in the text section (such as 'sectionA') , that is what I want to cross reference. If the bookmark text is the whole end note, that text will appear as the cross reference. I think this would be a simple change to the 'rngentry' range, but I'm not sure what to do with it.

I attached the test doc and deleted the A1 bookmark produced by the macro with one that consists only of the end note number and its prefix, so the entire bookmark is 'A1'. In the [1] end note reference in sectionA, I've created a cross reference to bookmark A1. This will retain the reference of [1] and link to its end note. If you create a cross ref for [2] in sectionA to bookmark A2, you'll see that it brings in the entire end note text (which is what I don't want).
GrahamSkanRetiredCommented:
I'm afraid that I'm now not sure what you need. Can you post a portion of the document that you have set up manually?
marrick13Author Commented:
Sorry about that, Graham, I thought I made it clear. I did set up the first end note reference and its corresponding bookmark that way in the latest version of the test document I posted. But I have expanded that a bit in this attached version as follows:

The sectionA bookmark captures the range of text that contains the body text and the end note references. If you select the sectionA bookmark and click 'goto' in the bookmark dialog box, you will see that text highlighted. The bracketed numbers in this section are [1], [2], [3], etc.

The sectionAA bookmark captures the range of the end notes for sectionA. SectionAA contains the same bracketed references as in sectionA. These are what I want the macro to bookmark--not the whole end note item, but just the end note reference, i.e., the bracketed number 1,2,3, etc. The end result is similar to what one would get by creating end notes with the Word interface, with the main difference being that the Word end note doesn't navigate to the note but displays its text in a hover. I want to navigate to the end note itself (and I also don't want to manually create Word end notes since the end note text is already in the document).

I have manually bookmarked the first five end notes that start on p. 15 and got o p. 16. You will see with the 'goto' that I created those five bookmarks by selecting only the reference number itself. In sectionA, I have created cross references to these five bookmarks according to their matching numbers. So if you go to sectionA, hover over any of the first five end note references, you'll see that they are cross referenced and linked so that clicking any one of them navigates to the corresponding end note in the end notes section (bookmark sectionAA) , starting on p. 15.

The idea behind this is so that I can add text to the first half of the document (body text with end note references) as well as the end notes for the additions to the end note section in the second half. Since each addition will have the same numbering from 1 to whatever (but varying numbers of end notes), I would create two new bookmarks for each: sectionB for the body text and sectionBB for the end notes, then sectionC for another body section and sectionCC for its end notes. The macro will create bookmarks from the end note numbers in sections AA,BB, CC, etc., and prefix them with the letter I set up (A, B, C, etc.). That way each section will have unique bookmarks even though their numbering sequence runs from 1 to the end for that section. I would build this document in steps, rnning the macro as I add to it, although once I get all the body text and end notes in and create their section bookmarks, I could just rn the macro, change the section variables, then run it again. The sections control which parts of the document the macro looks at.

Is this clearer? Thanks for your help and patience by the way!
GrahamSkanRetiredCommented:
It gets more complicated.

My latest macro is designed to separately bookmark the text of each endnote. The bookmark names comprise the bookmark number prefixed by a letter. The letter is currently hard-coded as "A".

Is this what happens when you run the macro? If not, I think I will need to see your result document. If it is than I will need to see your manually created version. Did you intend to attach the latter in your last comment?
marrick13Author Commented:
Yes, this is what your macro does (and does so at lightning speed, which is great). But I never wanted the end note text to be included in the bookmark; just the end note reference. If you run the code I first posted ("Shade_Between_Chars_Bkmk") it does make only the end note reference the bookmark (but has other issues, and errors out when it encounters a bracketed colon).

I was sure I attached the test doc previously, I have done so again this time. Again, I have manually created bookmarks for the first 5 end notes starting on p. 15 as well as cross references for their corresponding end note references. Please let me know it it comes through and if it shows better what I'm looking for. Thanks!
Insert-Bookmark-Test--Dec-15A-2017-.doc
GrahamSkanRetiredCommented:
Yes, you did sent the document. What I was actually asking for was the document after the macro had been executed to check that it was doing the same as it was doing here. However I now think (again) that I know what is needed, and it's even simpler.
Sub Bookmark_Between_Chars_Bkmk4()
    Dim myRange As Range
    Dim rngEntry As Range
    Dim uBookmark As String
    Dim blnSearchAgain As Boolean
    Dim strEntryNo As String
    Dim strBookMarkPrefix As String
    
    strBookMarkPrefix = "A"
    uBookmark = "lastsection"
    
    Set myRange = ActiveDocument.Bookmarks(uBookmark).Range 'instantiate ranges
    Set rngEntry = ActiveDocument.Bookmarks(uBookmark).Range
    
    With myRange.Find
        .Text = "\[[0-9]{1,}\]"
        .MatchWildcards = True
        Do While .Execute()
            Set rngEntry = myRange.Duplicate
            strEntryNo = Replace(Replace(myRange.Text, "[", ""), "]", "")
            myRange.Collapse direction:=wdCollapseEnd
            myRange.End = ActiveDocument.Bookmarks(uBookmark).Range.End
            ActiveDocument.Bookmarks.Add strBookMarkPrefix & strEntryNo, rngEntry
        Loop
    End With
 End Sub

Open in new window

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
marrick13Author Commented:
Yes-thank you! Just what I was after. Thanks again, Graham.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Microsoft Word

From novice to tech pro — start learning today.