PDA

View Full Version : save find all to an array



qazpl
10-04-2006, 09:48 PM
I want to search a word document for all instances of a certain word, for example "hello" and save the results of the search as an array of .range objects so I can manipulate it later in macro. I've done it in other apps but can't get it to work in ms word.
Thanks for yor time

TonyJollans
10-05-2006, 05:55 AM
It would be nice, wouldn't it? But I'm afraid you can't do it - except by building the array manually one find result at a time.

fumei
10-05-2006, 10:12 AM
Is that not what is being asked? To build that array?

In a code module:Option Explicit
Public myRanges() As Range

Sub BuildRangeArray()
Dim i As Integer

Selection.HomeKey Unit:=wdStory
With Selection.Find
Do While (.Execute(findtext:="Hello", _
Forward:=True) = True) = True
ReDim Preserve myRanges(i)
Set myRanges(i) = Selection.Range
i = i + 1
Loop
End With
End Sub

Sub TestRanges()
myRanges(4).Select
End Sub
The Sub TestRanges will select the Range of the fifth instance of "Hello"....assuming of course there is one. Oh, and assuming that the Sub BuildRangeArray was executed first!

Error trapping is required!

fumei
10-05-2006, 10:17 AM
Yeah, and you probably would put a .ClearFormatting in there as well....

fumei
10-05-2006, 10:24 AM
Oh, and if the array was to be used for other code - which of course it could be, otherwise what would be the point? - one may have to be careful. The Ranges that are in the array are the ranges of only the found FindText string. They do not contain any leading or following spaces.

qazpl
10-06-2006, 06:09 AM
Thanks All for the reply. I was really looking to do so without a loop since many times I am dealing with large documents with 30000 or more results and the loop goes pretty slow.
Thanks all

TonyJollans
10-06-2006, 09:03 AM
Might I ask why you need to build the array? Why can't you just find them you need them. Also what processing it is you are doing exactly. This is more than idle curiosity - there might be a better way to do what you want given that performance is an issue.

mvidas
10-06-2006, 11:35 AM
As Tony is suggesting, there are other ways of doing this. Instead of using Word's find method, you could use regular expressions (you may not even need the array if you want to just use the Match Collection). Heres an example, the bulk of the runtime is just creating a document with "The quick brown fox jumped over the lazy dogs. " 10,000 times:Sub qazplFindArray()
Dim i As Long, vStr As String, MatchArr() As Range
Dim RegEx As Object, RegC As Object, RegM As Match
Dim vTimer As Double

Documents.Add
For i = 1 To 10000
Selection.TypeText "The quick brown fox jumped over the lazy dogs. "
Next

'put document text into vStr variable
Selection.WholeStory
vStr = Selection.Text

'initialize RegExp object
Set RegEx = CreateObject("vbscript.regexp")
With RegEx
.Global = True
.MultiLine = True
.IgnoreCase = True
.Pattern = "The"
End With
'begin timer
vTimer = Timer
If RegEx.Test(vStr) Then
Set RegC = RegEx.Execute(vStr) 'RegC now holds a collection of every match
ReDim MatchArr(RegC.Count - 1)
i = 0
For Each RegM In RegC
Set MatchArr(i) = ActiveDocument.Range(RegM.FirstIndex, _
RegM.FirstIndex + RegM.Length)

Next
End If

'end timer
MsgBox "The range array was created in " & Timer - vTimer & " seconds"
End SubMatt

fumei
10-06-2006, 09:10 PM
Matt, very nice.

gazpl - you post a question on how to search for a word and get an array of Range objects for the results.

You have a solution. Now, you may have a problem with large document getting into the string variable that Matt uses. He selects the whole document and puts that in the string vStr.

I am with Tony on this one. You need to specify what it is you are doing, and wanting to do.

I would also suggest that when you post, that you post relevant requirements. Oh....say that it will be for huge documents and that there may be 30000 possible occurances of the searched for word.....seems a little relevant...hmmmmm?

In any case, you have two ways to make an array of Range objects that cover all instances of a looked for word. This answers your post.

If this does not work for you, please explain why not, and give actual details of what you want to do.

Frankly, I am having difficulties thinking about the usefulness of an array of 30000 Ranges.

qazpl
10-09-2006, 05:53 AM
Thanks all for your response and sorry for my being unclear and delayed. As for Tony's question, the reason I must find all instances is because I am searching a document for all its abbreviations through a wildcard search then sorting them and giving the user a dialog to change all similar abbreviations at one time. since I want to be able to changr the words and retain formatting I want to return a range object.
It seems like there isn't really a solution like in adobe scripting where "set mySearch = Find....." returns an array of the search. Thanks for the input and please reply anything you can add to help me.

fumei
10-10-2006, 11:45 AM
I don't understand. The solutions DO return an array of the search.

It seems you are mixing logic requirements. The solution, again, DOES return an array of your search. You search for "hello" and you get an array of all ranges of "hello". This is what you asked for. Your logic requirements, though, are more difficult than that.

The solutions offered - which meet what you asked for - return an array of an explicit search string. YOUR logic is now asking for a variable. OK, that certainly could be done.

But then that starts building more arrays. The solutions build an array for EACH string.

Nevertheless, with the solutions, you could get an array (for example) of ranges for all instances of "Can." Then offer the user the ability to change that abbreviation througout the document.

I used my procedure (although I think Matt has a better one) on a 100 page document with 6,000 + instances of "Can." to build the array myRanges. Then I ran: For var = 0 To UBound(myRanges())
myRanges(var).Text = "Canada"
NextIt took 37 seconds to change all the instances. As they ARE ranges, all formatting remains the same.

So I am not sure what exactly your problem is.

qazpl
10-10-2006, 07:32 PM
fumai, you are right the solutions provided above do work.
However I am looking for something faster which would be similar to the find all instances which is available from word xp and on. With such a search it would only take one line of code to fill an array. That is why I used Adobe Scripting to compare, because even though adobe incopy & indesign are slowwww saving a search to an array is considerably quicker with one line of code.
if I am still confusing you let me know and I will try again to explain my issue Thanks Fumei for your passion

fumei
10-11-2006, 04:38 AM
Sorry, but I am finding this more and more pointless.

You have not given any real reason for doing this. I am also a bit off with your statement:
However I am looking for something faster which would be similar to the find all instances which is available from word xp and onDoes that mean you are using 2000 or earlier? These pieces of information are important to tell us when you post.

I also think it very strange that you are trying to compare Adobe InDesign with Word. If this is what you are doing, then tell us...

Give me real numbers. HOW slow? What IS the difference? These vague comparisons, and vague (or NO) explanations of what you are wanting to do - REALLY want to do. Besides, it is silly to compare a layout application (Adobe InDesign) with a word processor (Word). They are very very different beasts.

Well, I think I am done here.

The bottom line is - I don't see the point of what you are doing, you have been given exactly what you asked for, and so.....

Good luck.

mvidas
10-11-2006, 07:05 AM
Gerry,
Agreed.

However after looking above I seem to have messed something up (though the version above doesnt match exactly what I have in my normal.dot file..?).
Anyways, I changed it to be a function:Function FindAll(ByRef vDoc As Document, ByVal vPattern As String, Optional _
ByVal vMatchCase As Boolean = False) As Range()
Dim i As Long, vStr As String, mArr() As Range, RegEx As Object, RegC As Object

'put document text into vStr variable
ReDim mArr(0)
Set mArr(0) = Selection.Range
Selection.WholeStory
vStr = Selection.Text
mArr(0).Select

'initialize RegExp object
Set RegEx = CreateObject("vbscript.regexp")
With RegEx
.Global = True
.MultiLine = True
.IgnoreCase = Not vMatchCase
.Pattern = vPattern
End With
ReDim mArr(0)
If RegEx.Test(vStr) Then
Set RegC = RegEx.Execute(vStr)
ReDim mArr(RegC.Count - 1)
For i = 0 To RegC.Count - 1
With RegC.Item(i)
Set mArr(i) = ActiveDocument.Range(.FirstIndex, .FirstIndex + .Length)
End With
Next
End If
FindAll = mArr
Set RegC = Nothing
Set RegEx = Nothing
Erase mArr
End Function
So if you have that in your project, you can get all the matched ranges (including wildcards if you want, using Regular Expressions (http://vbaexpress.com/kb/getarticle.php?kb_id=68) (link is excel example but same patterns) in a single line:Sub GetTheRangeExample()
Dim RG() As Range, vTimer As Double
vTimer = Timer
RG = FindAll(ActiveDocument, "the")
vTimer = Timer - vTimer
MsgBox "Found " & CStr(UBound(RG) + 1) & " instances of 'the' in document." & _
vbCrLf & "Function took " & Format(vTimer, "0.00") & " seconds."
Stop
'erasing your range array (either using this or when it hits 'end sub'
' could take a while depending on the size of it!
Erase RG
End SubThe 'single line' RG = FindAll(ActiveDocument, "the")obviously is calling the function, but you can use just the one line in multiple subs.
Matt

fumei
10-11-2006, 08:08 AM
Sorry, but that is so lame. All that so you can write it as a single line? Hmmmm.

1. You can do the very same thing using a Sub - not a Function - and call it with a single line. Call FindAll(ActiveDocument, "the")I tested both and they are identical in terms of speed in building the array. Which makes sense as the Sub and the Function use the same instructions.

2. Using the function in a Sub creates RG which is the array of ranges. RG itself has no properties. To do anything you have to use RG(x). So again, what are you DOING with this????

3. Using the function in a Sub allows the use of RG(x) only within the scope of that Sub. So it is still much better to make RG a Public variable...then you can use it anywhere, including multiple Subs.

mvidas
10-11-2006, 08:14 AM
(I think you confused me with the OP--was just giving another example) :)
EDIT: Also, the function could be good if you wanted to search for all instances of "fumei" and save that to one array, and save all instances of "mvidas" and save it to another array. Using a sub with a public variable could limit it's use (IMO)

fumei
10-11-2006, 08:28 AM
Ooops, sorry.

This is why I have been trying to find out what the purpose of this is.

The purpose of a public variable is to expand the ability to DO something with the array.

Yes, you could make different arrays for "fumei" and "mvidas". However, you would still have to declare them as separate arrays, and you would still only have them in scope of the sub that created them.

I am not disagreeing with you...it totally depends on what is the USE of the array.

I must admit though...I learned a bit from this thread. Which is always good.

mvidas
10-11-2006, 08:44 AM
While I'm as confused as you, often times I know I like to ask questions about things that won't actually accomplish anything other than knowing it can be done.
For what this could be used for (again, dont know why...):Sub GetTheRangeExample()
Dim fumeiRG() As Range, mvidasRG() As Range, tRG1 As Range, tRG2 As Range
Dim i As Long, j As Long
Documents.Add
Selection.TypeText GetWebIE("http://www.vbaexpress.com/forum/showthread.php?p=76518")
fumeiRG = FindAll(ActiveDocument, "fumei")
mvidasRG = FindAll(ActiveDocument, "mvidas")
For i = 0 To UBound(fumeiRG)
For j = 0 To UBound(mvidasRG)
Set tRG1 = fumeiRG(i)
tRG1.Expand wdParagraph
Set tRG2 = mvidasRG(j)
tRG2.Expand wdParagraph
If tRG1 = tRG2 Then
tRG1.Select
MsgBox "Found a paragraph with both words"
End If
Next
Next
End Sub
Function GetWebIE(ByVal vWebSite As String) As String
Dim IE As Object
Set IE = CreateObject("internetexplorer.application")
IE.Navigate2 vWebSite
Do While IE.readyState <> 4 'READYSTATE_COMPLETE
DoEvents
Loop
GetWebIE = IE.Document.Body.InnerText
Set IE = Nothing
End Function:)

I must admit though...I learned a bit from this thread. Which is always goodAgreed on both parts

fumei
10-11-2006, 05:04 PM
Matt...very neat. Cool.