PC Word Macro Not Working in Mac Word

E

Eric55

I have some find & replace macros in Word 2003 for PC that do not seem to
work in Word 2004 for Mac. The part of the macro that's not working is a loop
that goes through the document looking for 2 or more spaces and replaces with
one space. Another does the same for 2 or more blank paragraphs.

The portion of the code that covers the first example is (including the
lines at the beginning of the macro):

Selection.HomeKey Unit:=wdStory
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue
Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop
End With

At the end of the macro I call a subroutine that clears out the find/replace
parameters, something I got off the Word MVP site and which seems to be
necessary to make the macros work reliably in PC Word.

Any idea why this would not work in Word 2004? I tried another way of doing
a loop (see below), but that didn't work either (it didn't like what was in
the "text" field). Any help will be appreciated.

With Selection.Find
.Text = "^13{3,}"
.Replacement.Text = "^p^p"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
.Execute Replace:=wdReplaceAll
End With
 
J

John McGhie [MVP -- Word and Word Mac]

Hi Erik:

You have a "First iteration" problem. *My* code is full of them :)

Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop

This tests the control variable (Found) *before* you have executed the Find
statement. It will always be False... So your Execute will never be
executed.

You need to kludge it...

Selection.Find.Execute Replace:=wdReplaceAll
Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop

Duplicating the "Selection.Find.Execute Replace:=wdReplaceAll" fires it off
unconditionally the first time, ensuring that there is something to make the
..Find.Found result come true :)

Given that you are inside a With context, you can simplify your code a
little:

With Selection.Find
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
Do While .Found
.Execute Replace:=wdReplaceAll
Loop
End With

That should run on both platforms... Yup, it works here :)

Cheers
 
E

Eric55

John,

Wow, many thanks! This was a much more helpful reponse than I had hoped for.
I have only a basic knowledge of programming, and virtually none in using
VBA, so I have just tried to put a few macros together by piecing together
and modifying things I see on-line, but with only a weak understanding of
what's going on!

Let me ask a follow-up question to help advance my understanding a bit.
Related to my problem of not executing the Find operation before testing the
Found variable: in the simplified code that you suggested at the end of your
post, where does the Find operation execute? The "execute" seems to be
associated with the Replace operation. Thanks for your help.

--Eric
 
J

John McGhie [MVP - Word and Word Macintosh]

Hi Eric:

Wow, many thanks! This was a much more helpful reponse than I had hoped for.
I have only a basic knowledge of programming, and virtually none in using
VBA, so I have just tried to put a few macros together by piecing together
and modifying things I see on-line, but with only a weak understanding of
what's going on!

That's the *approved* procedure for obtaining success in VBA :) How do you
think *we* learned it. Oh, OK, there are those extremists who went out and
bought books on the subject and attended university and stuff. But they're
geeks...
Let me ask a follow-up question to help advance my understanding a bit.
Related to my problem of not executing the Find operation before testing the
Found variable: in the simplified code that you suggested at the end of your
post, where does the Find operation execute? The "execute" seems to be
associated with the Replace operation. Thanks for your help.

It's not, but you have not understood "dot-languages", "properties",
"inheritance", or "context". :)

You could thinks of the "dots" in the statement as "apostrophe esses". So
now we can read " Selection.Find.Execute " as "The Selection's Find's
Execute command".

VBA and AppleScript are both "object-oriented" languages. This means that
they are based upon "things" (objects). These objects are often physical
things we could see in a document if we looked (although they don't have to
be).

A document is an object, which contains objects, some of which contain
objects and so on. Like Chinese Eggs: big objects contain small objects
which contain smaller objects.

Objects have "properties" and "methods". Properties are like
"characteristics". My shirt is an object, it has a property of Size and a
property of Colour. The Colour property has a property of "Yellow" and (to
you) the size is unknown.

A "method" is like an "ability", literally "the ability to do something."

Let's look at the original statement:

Selection.Find.Execute Replace:=wdReplaceAll

To be strictly semantically correct, we should really write that as:
World.computer.OperatingSystem.Application.Documents.ActiveDocument.
Selection.Find.Execute

However, we can indulge in a little assumption in the interests of brevity.
We can assume that the whole world will not let us play with its computers,
so the computer in question can be implied to be "the computer upon which
our program is executing. Similarly, we can assume that there can be only
operating system controlling this computer.

"Application" is going to be tricky: there could be several. We should
really name it, but even that wouldn't do us much good because there may be
more than one copy running.

Instead, we rely on the rules of "Context". "Context" tells us to "use the
closest one we find to where we are standing." If we are standing in the
room in which I work, the context is "my office" and "the door" thus needs
to further information because there is only one door to my office. If we
were standing in the corridor, we would have to say "which" door.

Since, in VBA, we are running within the context of a single application, we
do not have to say which one we mean unless we mean one that is outside of
where we are running. So in this case, VBA knows that the "Application" in
question is "this copy of Word".

Now pay attention: this bit gets a little obtuse...

Word can have multiple documents open. So in VBA we need to say which
document we mean. However, because we're lazy, we can allow that to default
to "the active document" (which is ALWAYS the document containing the
selection). We are relying on the fact that Word can have only ONE
selection, and that the document that contains it must be the active
document, and that these two properties will default, so we do not have to
say "which" document or which selection because there can be only one
selection, and if we find that we automatically know which document we're
talking about.

The "Selection" is an object. It has a method: "Find" (it knows how to find
things). So once we have narrowed the context down to the selection, we can
then refer to "Find" unambiguously, because there can be only one selection
and thus only one Find.

So we're now several layers deep into this puzzle. However, because there
is no chance of ambiguity, we do not have to qualify anything yet. Because
there is no chance of any alternatives, we can use some smarts built in to
the VBA compiler to save ourselves a lot of typing.

The first thing we can do is explicitly nominate our context. Because our
whole code block refers to the same context, we can nominate it up front:
"With Selection.Find" All the following statements up to the "End With"
will now operate only within the explicit context described above -- the
selection in the active document.

So now we can remove the qualifying context from each statement: instead of
having to write "Selection.Find" in front of each statement, we can write it
once and have it apply to the entire block.

Once we do that, we can use a leading dot to IMPLY the context. That's what
I am doing when I write:
Do While .Found
.Execute Replace:=wdReplaceAll
Loop

The dot in front of the word .Found implies the current context so that bit
means "Do while Selection's Find's Found property (exists)." I could
equally have written "Do While Selection.Find.Found = True". But I know I
can rely on another behaviour of VBA: if I do not qualify a "While"
statement with an operator and an operand, the compiler's default is to
check for "True", and since I know the built-in control variable "Found" is
Boolean, I know it can only be one or the other, true or false.

The dot in front of Execute simply implies the context again: "Selection's
Find's Execute method".

There's nothing in the rules to say that an Object can not contain other
objects. So the Find object can (and does...) contain a Replacement object.
As soon as you name the Find object as being in your context, the compiler
expects you to do something with the Replacement object. We could, of
course, choose not to use it, but we don't.

Instead, we fire the "Execute" method of the Find object, and set its
"Replace" property to "wdReplaceAll" (change each instance that Find finds).

Now we have to avoid getting confused by the "replace all". It means
"replace all the instances that THIS iteration of the Find method found. As
we know, if you have four spaces, and you search for two spaces and change
to one, the first two spaces will be turned into one space, but then the
find will inspect the text FOLLOWING the replacement. It will change those
two into one also. But you will then be left with two spaces where you
previously had four. So we need to run the Find at least one more time to
find them.

In our case, we choose to keep running the Find command until it doesn't
find anything. Each time we run it, it will inspect all of the text in the
document (because we set Wrap to 'wdFindContinue'). When it doesn't find
anything, Find will return with the control variable Found set to False and
our macro then stops trying and exits.

So: the implied question is "should we use shorthand in code?" There is no
"correct" answer. Each individual case is an artistic, not scientific,
choice.

So long as our code is unambiguous, the compiler will produce exactly the
same binary each time. So the question becomes "who else needs to read this
text." If the answer is "Only Me", and you have a good memory, you can be
as cryptic as you like. If you have lots of experience with VBA and know
the language well, you can save yourself a great deal of typing and reading.

However, if someone else has to try to work out how to maintain the code
some years later when you're not around or can't remember how it works, they
will really thank you for letting your text run on a little, leaving a few
more words in there to make it self-explanatory.

Here's an example: I use this as an "exam" for job applicants. If someone
claims to be expert in Word VBA, I show them this and ask them what it does:

With myRange.Paragraphs
.Style = "Table Number"
With .First.Range.ListFormat
.ApplyListTemplate .ListTemplate, False
End With
With .Last
.KeepWithNext = False
.SpaceAfter = 4
End With
End With

There's a prize for the most correct guess. Those who've seen it before, be
quiet: let the new students have some fun!

Cheers

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Consultant Technical Writer
Sydney, Australia +61 (0) 4 1209 1410
 
E

Eric55

John,

Again, many thanks. This was extremely helpful. I've read a little about
VBA, but the terminology is hard to really internalize without examples,
practice, and further explanation. Your translation of the definitions into
other words really helps clarify things.

The exam is a fun challenge! Unfortuntely, there's no way that I could give
a decent answer without further reading & study. Maybe I can come up with a
"passing" answer in the near future.

It's hard to imagine being able to write macros from scratch, however, as
compared to modifying an example you get from somewhere else. The list of
possible objects, methods, properties, etc., seems very long--not clear which
ones really apply to a given problem. I guess it just takes time and
concentrated effort to learn.

If I haven't exhausted your patience, can I ask a further question about the
specific macro I was working on? In the version I had for PC Word, I had a
section of code that I got from an article by Dave Rado on how to delete
empty paragraphs, which contained the following:

With Selection.Find
.Text = "^13{2,}"
.Replacement.Text = "^p^p"
(etc.)

When I tried to run this in Mac Word, however, I got a message that said
"Run-time error '5560' The Find What text contains a Pattern Match expression
which is not valid." Is this a result of different versions of VBA being used
in PC vs. Mac Word, or is there some other explanation?

Many thanks!

--Eric





John McGhie [MVP - Word and Word Macinto said:
Hi Eric:

Wow, many thanks! This was a much more helpful reponse than I had hoped for.
I have only a basic knowledge of programming, and virtually none in using
VBA, so I have just tried to put a few macros together by piecing together
and modifying things I see on-line, but with only a weak understanding of
what's going on!

That's the *approved* procedure for obtaining success in VBA :) How do you
think *we* learned it. Oh, OK, there are those extremists who went out and
bought books on the subject and attended university and stuff. But they're
geeks...
Let me ask a follow-up question to help advance my understanding a bit.
Related to my problem of not executing the Find operation before testing the
Found variable: in the simplified code that you suggested at the end of your
post, where does the Find operation execute? The "execute" seems to be
associated with the Replace operation. Thanks for your help.

It's not, but you have not understood "dot-languages", "properties",
"inheritance", or "context". :)

You could thinks of the "dots" in the statement as "apostrophe esses". So
now we can read " Selection.Find.Execute " as "The Selection's Find's
Execute command".

VBA and AppleScript are both "object-oriented" languages. This means that
they are based upon "things" (objects). These objects are often physical
things we could see in a document if we looked (although they don't have to
be).

A document is an object, which contains objects, some of which contain
objects and so on. Like Chinese Eggs: big objects contain small objects
which contain smaller objects.

Objects have "properties" and "methods". Properties are like
"characteristics". My shirt is an object, it has a property of Size and a
property of Colour. The Colour property has a property of "Yellow" and (to
you) the size is unknown.

A "method" is like an "ability", literally "the ability to do something."

Let's look at the original statement:

Selection.Find.Execute Replace:=wdReplaceAll

To be strictly semantically correct, we should really write that as:
World.computer.OperatingSystem.Application.Documents.ActiveDocument.
Selection.Find.Execute

However, we can indulge in a little assumption in the interests of brevity.
We can assume that the whole world will not let us play with its computers,
so the computer in question can be implied to be "the computer upon which
our program is executing. Similarly, we can assume that there can be only
operating system controlling this computer.

"Application" is going to be tricky: there could be several. We should
really name it, but even that wouldn't do us much good because there may be
more than one copy running.

Instead, we rely on the rules of "Context". "Context" tells us to "use the
closest one we find to where we are standing." If we are standing in the
room in which I work, the context is "my office" and "the door" thus needs
to further information because there is only one door to my office. If we
were standing in the corridor, we would have to say "which" door.

Since, in VBA, we are running within the context of a single application, we
do not have to say which one we mean unless we mean one that is outside of
where we are running. So in this case, VBA knows that the "Application" in
question is "this copy of Word".

Now pay attention: this bit gets a little obtuse...

Word can have multiple documents open. So in VBA we need to say which
document we mean. However, because we're lazy, we can allow that to default
to "the active document" (which is ALWAYS the document containing the
selection). We are relying on the fact that Word can have only ONE
selection, and that the document that contains it must be the active
document, and that these two properties will default, so we do not have to
say "which" document or which selection because there can be only one
selection, and if we find that we automatically know which document we're
talking about.

The "Selection" is an object. It has a method: "Find" (it knows how to find
things). So once we have narrowed the context down to the selection, we can
then refer to "Find" unambiguously, because there can be only one selection
and thus only one Find.

So we're now several layers deep into this puzzle. However, because there
is no chance of ambiguity, we do not have to qualify anything yet. Because
there is no chance of any alternatives, we can use some smarts built in to
the VBA compiler to save ourselves a lot of typing.

The first thing we can do is explicitly nominate our context. Because our
whole code block refers to the same context, we can nominate it up front:
"With Selection.Find" All the following statements up to the "End With"
will now operate only within the explicit context described above -- the
selection in the active document.

So now we can remove the qualifying context from each statement: instead of
having to write "Selection.Find" in front of each statement, we can write it
once and have it apply to the entire block.

Once we do that, we can use a leading dot to IMPLY the context. That's what
I am doing when I write:
Do While .Found
.Execute Replace:=wdReplaceAll
Loop

The dot in front of the word .Found implies the current context so that bit
means "Do while Selection's Find's Found property (exists)." I could
equally have written "Do While Selection.Find.Found = True". But I know I
can rely on another behaviour of VBA: if I do not qualify a "While"
statement with an operator and an operand, the compiler's default is to
check for "True", and since I know the built-in control variable "Found" is
Boolean, I know it can only be one or the other, true or false.

The dot in front of Execute simply implies the context again: "Selection's
Find's Execute method".

There's nothing in the rules to say that an Object can not contain other
objects. So the Find object can (and does...) contain a Replacement object.
As soon as you name the Find object as being in your context, the compiler
expects you to do something with the Replacement object. We could, of
course, choose not to use it, but we don't.

Instead, we fire the "Execute" method of the Find object, and set its
"Replace" property to "wdReplaceAll" (change each instance that Find finds).

Now we have to avoid getting confused by the "replace all". It means
"replace all the instances that THIS iteration of the Find method found. As
we know, if you have four spaces, and you search for two spaces and change
to one, the first two spaces will be turned into one space, but then the
find will inspect the text FOLLOWING the replacement. It will change those
two into one also. But you will then be left with two spaces where you
previously had four. So we need to run the Find at least one more time to
find them.

In our case, we choose to keep running the Find command until it doesn't
find anything. Each time we run it, it will inspect all of the text in the
document (because we set Wrap to 'wdFindContinue'). When it doesn't find
anything, Find will return with the control variable Found set to False and
our macro then stops trying and exits.

So: the implied question is "should we use shorthand in code?" There is no
"correct" answer. Each individual case is an artistic, not scientific,
choice.

So long as our code is unambiguous, the compiler will produce exactly the
same binary each time. So the question becomes "who else needs to read this
text." If the answer is "Only Me", and you have a good memory, you can be
as cryptic as you like. If you have lots of experience with VBA and know
the language well, you can save yourself a great deal of typing and reading.

However, if someone else has to try to work out how to maintain the code
some years later when you're not around or can't remember how it works, they
will really thank you for letting your text run on a little, leaving a few
more words in there to make it self-explanatory.

Here's an example: I use this as an "exam" for job applicants. If someone
claims to be expert in Word VBA, I show them this and ask them what it does:

With myRange.Paragraphs
.Style = "Table Number"
With .First.Range.ListFormat
.ApplyListTemplate .ListTemplate, False
End With
With .Last
.KeepWithNext = False
.SpaceAfter = 4
End With
End With

There's a prize for the most correct guess. Those who've seen it before, be
quiet: let the new students have some fun!

Cheers

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Consultant Technical Writer
Sydney, Australia +61 (0) 4 1209 1410
 
J

John McGhie [MVP - Word and Word Macintosh]

Hi Eric:

The exam is a fun challenge! Unfortuntely, there's no way that I could give
a decent answer without further reading & study. Maybe I can come up with a
"passing" answer in the near future.

There are two other people in this group who I *know* know the answer. I am
surprised that the others haven't at least had a go at it. Chickens!!
It's hard to imagine being able to write macros from scratch, however, as
compared to modifying an example you get from somewhere else.

NOBODY does that :) (Well, I do, occasionally, but only for very simple
stuff...). Not only is stealing code the best way to look like a hero, it's
also the only way to get the project completed and tested on time!

Also: The Mac VBA Editor is a savagely-castrated shadow of the one in Word
2003. I don't think ANYONE who has to write much in the way of VBA would
even think of attempting it in Mac Word. I either Remote Desktop Connection
into my PC, if it's running, or fire up Virtual PC and Word 2003. I get the
code running and stable and debugged before I paste it into Mac Word 2004
for final testing.

The extra smarts in the Word 2003 VBE and the extra information in the Word
2003 Help just save you weeks writing anything substantial. For example, in
the PC version, you can stop the action at any specific statement and read
the contents of all of the properties, variables and objects in the macro as
they are at that point. You can't DO that on the Mac, it doesn't support
the required view.
The list of
possible objects, methods, properties, etc., seems very long--not clear which
ones really apply to a given problem. I guess it just takes time and
concentrated effort to learn.

Nope: You begin by "Recording" what you are trying to do :) Then all you
have to do is add the IF statements and branching and control logic. But
let the Macro Recorder write the bulk of the code for you. It will save you
looking up all those things: it automatically uses the correct things.
Furthermore: VBA is an evolving language that has grown up over time. It's
riddled with inconsistencies and workarounds. There are some things that
plain don't work, even though they should. If you use the macro recorder,
it will automatically record "the one that works" if there are choices. And
sometimes (for example, when you switch into or out of the Header and Footer
View) it substitutes some pre-written VBA code it has stored because it
KNOWS that the correct properties simply don't work...
If I haven't exhausted your patience, can I ask a further question about the
specific macro I was working on? In the version I had for PC Word, I had a
section of code that I got from an article by Dave Rado on how to delete
empty paragraphs, which contained the following:

With Selection.Find
.Text = "^13{2,}"
.Replacement.Text = "^p^p"
(etc.)

It's a bug. The pattern matching specification doesn't work with the ANSI
code specification. Use:

Sub Macro1()
'
' Macro1 Macro
' Macro recorded 3/9/06 by John McGhie
'
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "^p^p"
.Replacement.Text = "^p"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
End Sub


Cheers
John McGhie [MVP - Word and Word Macinto said:
Hi Eric:

Wow, many thanks! This was a much more helpful reponse than I had hoped for.
I have only a basic knowledge of programming, and virtually none in using
VBA, so I have just tried to put a few macros together by piecing together
and modifying things I see on-line, but with only a weak understanding of
what's going on!

That's the *approved* procedure for obtaining success in VBA :) How do you
think *we* learned it. Oh, OK, there are those extremists who went out and
bought books on the subject and attended university and stuff. But they're
geeks...
Let me ask a follow-up question to help advance my understanding a bit.
Related to my problem of not executing the Find operation before testing the
Found variable: in the simplified code that you suggested at the end of your
post, where does the Find operation execute? The "execute" seems to be
associated with the Replace operation. Thanks for your help.

It's not, but you have not understood "dot-languages", "properties",
"inheritance", or "context". :)

You could thinks of the "dots" in the statement as "apostrophe esses". So
now we can read " Selection.Find.Execute " as "The Selection's Find's
Execute command".

VBA and AppleScript are both "object-oriented" languages. This means that
they are based upon "things" (objects). These objects are often physical
things we could see in a document if we looked (although they don't have to
be).

A document is an object, which contains objects, some of which contain
objects and so on. Like Chinese Eggs: big objects contain small objects
which contain smaller objects.

Objects have "properties" and "methods". Properties are like
"characteristics". My shirt is an object, it has a property of Size and a
property of Colour. The Colour property has a property of "Yellow" and (to
you) the size is unknown.

A "method" is like an "ability", literally "the ability to do something."

Let's look at the original statement:

Selection.Find.Execute Replace:=wdReplaceAll

To be strictly semantically correct, we should really write that as:
World.computer.OperatingSystem.Application.Documents.ActiveDocument.
Selection.Find.Execute

However, we can indulge in a little assumption in the interests of brevity.
We can assume that the whole world will not let us play with its computers,
so the computer in question can be implied to be "the computer upon which
our program is executing. Similarly, we can assume that there can be only
operating system controlling this computer.

"Application" is going to be tricky: there could be several. We should
really name it, but even that wouldn't do us much good because there may be
more than one copy running.

Instead, we rely on the rules of "Context". "Context" tells us to "use the
closest one we find to where we are standing." If we are standing in the
room in which I work, the context is "my office" and "the door" thus needs
to further information because there is only one door to my office. If we
were standing in the corridor, we would have to say "which" door.

Since, in VBA, we are running within the context of a single application, we
do not have to say which one we mean unless we mean one that is outside of
where we are running. So in this case, VBA knows that the "Application" in
question is "this copy of Word".

Now pay attention: this bit gets a little obtuse...

Word can have multiple documents open. So in VBA we need to say which
document we mean. However, because we're lazy, we can allow that to default
to "the active document" (which is ALWAYS the document containing the
selection). We are relying on the fact that Word can have only ONE
selection, and that the document that contains it must be the active
document, and that these two properties will default, so we do not have to
say "which" document or which selection because there can be only one
selection, and if we find that we automatically know which document we're
talking about.

The "Selection" is an object. It has a method: "Find" (it knows how to find
things). So once we have narrowed the context down to the selection, we can
then refer to "Find" unambiguously, because there can be only one selection
and thus only one Find.

So we're now several layers deep into this puzzle. However, because there
is no chance of ambiguity, we do not have to qualify anything yet. Because
there is no chance of any alternatives, we can use some smarts built in to
the VBA compiler to save ourselves a lot of typing.

The first thing we can do is explicitly nominate our context. Because our
whole code block refers to the same context, we can nominate it up front:
"With Selection.Find" All the following statements up to the "End With"
will now operate only within the explicit context described above -- the
selection in the active document.

So now we can remove the qualifying context from each statement: instead of
having to write "Selection.Find" in front of each statement, we can write it
once and have it apply to the entire block.

Once we do that, we can use a leading dot to IMPLY the context. That's what
I am doing when I write:
Do While .Found
.Execute Replace:=wdReplaceAll
Loop

The dot in front of the word .Found implies the current context so that bit
means "Do while Selection's Find's Found property (exists)." I could
equally have written "Do While Selection.Find.Found = True". But I know I
can rely on another behaviour of VBA: if I do not qualify a "While"
statement with an operator and an operand, the compiler's default is to
check for "True", and since I know the built-in control variable "Found" is
Boolean, I know it can only be one or the other, true or false.

The dot in front of Execute simply implies the context again: "Selection's
Find's Execute method".

There's nothing in the rules to say that an Object can not contain other
objects. So the Find object can (and does...) contain a Replacement object.
As soon as you name the Find object as being in your context, the compiler
expects you to do something with the Replacement object. We could, of
course, choose not to use it, but we don't.

Instead, we fire the "Execute" method of the Find object, and set its
"Replace" property to "wdReplaceAll" (change each instance that Find finds).

Now we have to avoid getting confused by the "replace all". It means
"replace all the instances that THIS iteration of the Find method found. As
we know, if you have four spaces, and you search for two spaces and change
to one, the first two spaces will be turned into one space, but then the
find will inspect the text FOLLOWING the replacement. It will change those
two into one also. But you will then be left with two spaces where you
previously had four. So we need to run the Find at least one more time to
find them.

In our case, we choose to keep running the Find command until it doesn't
find anything. Each time we run it, it will inspect all of the text in the
document (because we set Wrap to 'wdFindContinue'). When it doesn't find
anything, Find will return with the control variable Found set to False and
our macro then stops trying and exits.

So: the implied question is "should we use shorthand in code?" There is no
"correct" answer. Each individual case is an artistic, not scientific,
choice.

So long as our code is unambiguous, the compiler will produce exactly the
same binary each time. So the question becomes "who else needs to read this
text." If the answer is "Only Me", and you have a good memory, you can be
as cryptic as you like. If you have lots of experience with VBA and know
the language well, you can save yourself a great deal of typing and reading.

However, if someone else has to try to work out how to maintain the code
some years later when you're not around or can't remember how it works, they
will really thank you for letting your text run on a little, leaving a few
more words in there to make it self-explanatory.

Here's an example: I use this as an "exam" for job applicants. If someone
claims to be expert in Word VBA, I show them this and ask them what it does:

With myRange.Paragraphs
.Style = "Table Number"
With .First.Range.ListFormat
.ApplyListTemplate .ListTemplate, False
End With
With .Last
.KeepWithNext = False
.SpaceAfter = 4
End With
End With

There's a prize for the most correct guess. Those who've seen it before, be
quiet: let the new students have some fun!

Cheers
--Eric

:

Hi Erik:

You have a "First iteration" problem. *My* code is full of them :)

Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop

This tests the control variable (Found) *before* you have executed the Find
statement. It will always be False... So your Execute will never be
executed.

You need to kludge it...

Selection.Find.Execute Replace:=wdReplaceAll
Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop

Duplicating the "Selection.Find.Execute Replace:=wdReplaceAll" fires it
off
unconditionally the first time, ensuring that there is something to make
the
..Find.Found result come true :)

Given that you are inside a With context, you can simplify your code a
little:

With Selection.Find
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
Do While .Found
.Execute Replace:=wdReplaceAll
Loop
End With

That should run on both platforms... Yup, it works here :)

Cheers

--

John McGhie <[email protected]>
Consultant Technical Writer, Microsoft MVP (Word, Word for Mac)
Sydney, Australia +61 (0)4 1209 1410

I have some find & replace macros in Word 2003 for PC that do not seem to
work in Word 2004 for Mac. The part of the macro that's not working is a
loop
that goes through the document looking for 2 or more spaces and replaces
with
one space. Another does the same for 2 or more blank paragraphs.

The portion of the code that covers the first example is (including the
lines at the beginning of the macro):

Selection.HomeKey Unit:=wdStory
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue
Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop
End With

At the end of the macro I call a subroutine that clears out the
find/replace
parameters, something I got off the Word MVP site and which seems to be
necessary to make the macros work reliably in PC Word.

Any idea why this would not work in Word 2004? I tried another way of
doing
a loop (see below), but that didn't work either (it didn't like what was
in
the "text" field). Any help will be appreciated.

With Selection.Find
.Text = "^13{3,}"
.Replacement.Text = "^p^p"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
.Execute Replace:=wdReplaceAll
End With

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Consultant Technical Writer
Sydney, Australia +61 (0) 4 1209 1410

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Consultant Technical Writer
Sydney, Australia +61 (0) 4 1209 1410
 
E

Eric55

John,

OK, thanks for the additional information. This is helping me learn a lot.

Now, unfortunately, I have to return to my original question because after
using the code you suggested the looping process in the macro still doesn't
work in Mac Word 2004, though it works fine in PC Word 2003. (By the way, in
case it's relevant, I'm using ver. 11.2 of Mac Word 2004 and Mac OS X 10.4.7
on a 2.1 GHz iMac Power PC G5 with 1 GB Ram.)

To simplify things, I did a test macro with basically just the code you
suggested, as follows:

Sub Test()

Selection.HomeKey Unit:=wdStory
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
Do While .Found
.Execute Replace:=wdReplaceAll
Loop
End With
End Sub

I then did a short Word document with a couple of sentences with multiple
spaces between some of the words. In Mac Word, the test macro seems to run OK
(no error messages) but it does not eliminate all the double spaces. On my PC
running Word 2003, the test macro works perfectly.

So, I'm baffled! I tried moving the While condition from the Do to the Loop
statement, but that didn't help. Is this problem a result of differences
between PC and Mac VBA? It seems hard to believe that basic Find and Replace
operations would be handled differently.

My apologies for pestering you further with this.

Thanks for your help.

--Eric

John McGhie [MVP - Word and Word Macinto said:
Hi Eric:

The exam is a fun challenge! Unfortuntely, there's no way that I could give
a decent answer without further reading & study. Maybe I can come up with a
"passing" answer in the near future.

There are two other people in this group who I *know* know the answer. I am
surprised that the others haven't at least had a go at it. Chickens!!
It's hard to imagine being able to write macros from scratch, however, as
compared to modifying an example you get from somewhere else.

NOBODY does that :) (Well, I do, occasionally, but only for very simple
stuff...). Not only is stealing code the best way to look like a hero, it's
also the only way to get the project completed and tested on time!

Also: The Mac VBA Editor is a savagely-castrated shadow of the one in Word
2003. I don't think ANYONE who has to write much in the way of VBA would
even think of attempting it in Mac Word. I either Remote Desktop Connection
into my PC, if it's running, or fire up Virtual PC and Word 2003. I get the
code running and stable and debugged before I paste it into Mac Word 2004
for final testing.

The extra smarts in the Word 2003 VBE and the extra information in the Word
2003 Help just save you weeks writing anything substantial. For example, in
the PC version, you can stop the action at any specific statement and read
the contents of all of the properties, variables and objects in the macro as
they are at that point. You can't DO that on the Mac, it doesn't support
the required view.
The list of
possible objects, methods, properties, etc., seems very long--not clear which
ones really apply to a given problem. I guess it just takes time and
concentrated effort to learn.

Nope: You begin by "Recording" what you are trying to do :) Then all you
have to do is add the IF statements and branching and control logic. But
let the Macro Recorder write the bulk of the code for you. It will save you
looking up all those things: it automatically uses the correct things.
Furthermore: VBA is an evolving language that has grown up over time. It's
riddled with inconsistencies and workarounds. There are some things that
plain don't work, even though they should. If you use the macro recorder,
it will automatically record "the one that works" if there are choices. And
sometimes (for example, when you switch into or out of the Header and Footer
View) it substitutes some pre-written VBA code it has stored because it
KNOWS that the correct properties simply don't work...
If I haven't exhausted your patience, can I ask a further question about the
specific macro I was working on? In the version I had for PC Word, I had a
section of code that I got from an article by Dave Rado on how to delete
empty paragraphs, which contained the following:

With Selection.Find
.Text = "^13{2,}"
.Replacement.Text = "^p^p"
(etc.)

It's a bug. The pattern matching specification doesn't work with the ANSI
code specification. Use:

Sub Macro1()
'
' Macro1 Macro
' Macro recorded 3/9/06 by John McGhie
'
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "^p^p"
.Replacement.Text = "^p"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
End Sub


Cheers
John McGhie [MVP - Word and Word Macinto said:
Hi Eric:

On 2/9/06 1:12 PM, in article
(e-mail address removed), "Eric55"

Wow, many thanks! This was a much more helpful reponse than I had hoped for.
I have only a basic knowledge of programming, and virtually none in using
VBA, so I have just tried to put a few macros together by piecing together
and modifying things I see on-line, but with only a weak understanding of
what's going on!

That's the *approved* procedure for obtaining success in VBA :) How do you
think *we* learned it. Oh, OK, there are those extremists who went out and
bought books on the subject and attended university and stuff. But they're
geeks...

Let me ask a follow-up question to help advance my understanding a bit.
Related to my problem of not executing the Find operation before testing the
Found variable: in the simplified code that you suggested at the end of your
post, where does the Find operation execute? The "execute" seems to be
associated with the Replace operation. Thanks for your help.

It's not, but you have not understood "dot-languages", "properties",
"inheritance", or "context". :)

You could thinks of the "dots" in the statement as "apostrophe esses". So
now we can read " Selection.Find.Execute " as "The Selection's Find's
Execute command".

VBA and AppleScript are both "object-oriented" languages. This means that
they are based upon "things" (objects). These objects are often physical
things we could see in a document if we looked (although they don't have to
be).

A document is an object, which contains objects, some of which contain
objects and so on. Like Chinese Eggs: big objects contain small objects
which contain smaller objects.

Objects have "properties" and "methods". Properties are like
"characteristics". My shirt is an object, it has a property of Size and a
property of Colour. The Colour property has a property of "Yellow" and (to
you) the size is unknown.

A "method" is like an "ability", literally "the ability to do something."

Let's look at the original statement:

Selection.Find.Execute Replace:=wdReplaceAll

To be strictly semantically correct, we should really write that as:
World.computer.OperatingSystem.Application.Documents.ActiveDocument.
Selection.Find.Execute

However, we can indulge in a little assumption in the interests of brevity.
We can assume that the whole world will not let us play with its computers,
so the computer in question can be implied to be "the computer upon which
our program is executing. Similarly, we can assume that there can be only
operating system controlling this computer.

"Application" is going to be tricky: there could be several. We should
really name it, but even that wouldn't do us much good because there may be
more than one copy running.

Instead, we rely on the rules of "Context". "Context" tells us to "use the
closest one we find to where we are standing." If we are standing in the
room in which I work, the context is "my office" and "the door" thus needs
to further information because there is only one door to my office. If we
were standing in the corridor, we would have to say "which" door.

Since, in VBA, we are running within the context of a single application, we
do not have to say which one we mean unless we mean one that is outside of
where we are running. So in this case, VBA knows that the "Application" in
question is "this copy of Word".

Now pay attention: this bit gets a little obtuse...

Word can have multiple documents open. So in VBA we need to say which
document we mean. However, because we're lazy, we can allow that to default
to "the active document" (which is ALWAYS the document containing the
selection). We are relying on the fact that Word can have only ONE
selection, and that the document that contains it must be the active
document, and that these two properties will default, so we do not have to
say "which" document or which selection because there can be only one
selection, and if we find that we automatically know which document we're
talking about.

The "Selection" is an object. It has a method: "Find" (it knows how to find
things). So once we have narrowed the context down to the selection, we can
then refer to "Find" unambiguously, because there can be only one selection
and thus only one Find.

So we're now several layers deep into this puzzle. However, because there
is no chance of ambiguity, we do not have to qualify anything yet. Because
there is no chance of any alternatives, we can use some smarts built in to
the VBA compiler to save ourselves a lot of typing.

The first thing we can do is explicitly nominate our context. Because our
whole code block refers to the same context, we can nominate it up front:
"With Selection.Find" All the following statements up to the "End With"
will now operate only within the explicit context described above -- the
selection in the active document.

So now we can remove the qualifying context from each statement: instead of
having to write "Selection.Find" in front of each statement, we can write it
once and have it apply to the entire block.

Once we do that, we can use a leading dot to IMPLY the context. That's what
I am doing when I write:
Do While .Found
.Execute Replace:=wdReplaceAll
Loop

The dot in front of the word .Found implies the current context so that bit
means "Do while Selection's Find's Found property (exists)." I could
equally have written "Do While Selection.Find.Found = True". But I know I
can rely on another behaviour of VBA: if I do not qualify a "While"
statement with an operator and an operand, the compiler's default is to
check for "True", and since I know the built-in control variable "Found" is
Boolean, I know it can only be one or the other, true or false.

The dot in front of Execute simply implies the context again: "Selection's
Find's Execute method".

There's nothing in the rules to say that an Object can not contain other
objects. So the Find object can (and does...) contain a Replacement object.
As soon as you name the Find object as being in your context, the compiler
expects you to do something with the Replacement object. We could, of
course, choose not to use it, but we don't.

Instead, we fire the "Execute" method of the Find object, and set its
"Replace" property to "wdReplaceAll" (change each instance that Find finds).

Now we have to avoid getting confused by the "replace all". It means
"replace all the instances that THIS iteration of the Find method found. As
we know, if you have four spaces, and you search for two spaces and change
to one, the first two spaces will be turned into one space, but then the
find will inspect the text FOLLOWING the replacement. It will change those
two into one also. But you will then be left with two spaces where you
previously had four. So we need to run the Find at least one more time to
find them.

In our case, we choose to keep running the Find command until it doesn't
find anything. Each time we run it, it will inspect all of the text in the
document (because we set Wrap to 'wdFindContinue'). When it doesn't find
anything, Find will return with the control variable Found set to False and
our macro then stops trying and exits.

So: the implied question is "should we use shorthand in code?" There is no
"correct" answer. Each individual case is an artistic, not scientific,
choice.

So long as our code is unambiguous, the compiler will produce exactly the
same binary each time. So the question becomes "who else needs to read this
text." If the answer is "Only Me", and you have a good memory, you can be
as cryptic as you like. If you have lots of experience with VBA and know
the language well, you can save yourself a great deal of typing and reading.

However, if someone else has to try to work out how to maintain the code
some years later when you're not around or can't remember how it works, they
will really thank you for letting your text run on a little, leaving a few
more words in there to make it self-explanatory.

Here's an example: I use this as an "exam" for job applicants. If someone
claims to be expert in Word VBA, I show them this and ask them what it does:

With myRange.Paragraphs
.Style = "Table Number"
With .First.Range.ListFormat
.ApplyListTemplate .ListTemplate, False
End With
With .Last
.KeepWithNext = False
.SpaceAfter = 4
End With
End With

There's a prize for the most correct guess. Those who've seen it before, be
quiet: let the new students have some fun!

Cheers


--Eric

:

Hi Erik:

You have a "First iteration" problem. *My* code is full of them :)

Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop

This tests the control variable (Found) *before* you have executed the Find
statement. It will always be False... So your Execute will never be
executed.

You need to kludge it...

Selection.Find.Execute Replace:=wdReplaceAll
Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop
 
J

John McGhie [MVP - Word and Word Macintosh]

Hi Eric:

You're not pestering me, you are helping me to indulge my hobby. My hobby
is to answer questions: that's what I come here to do. I leave unsatisfied
if there are not enough questions or they're all boring or from rude people
who are too stupid to look in the Help before asking :) so you're helping
me get my fix :)

Now: Hmmm... I wrote my original in Mac Word, it shoulda worked...

Right!!! Officially NOT HAPPY!!!

There appears to be a NASTY bug in Mac Word. The ".Found" system variable
that is intrinsic to Word 2003 does not appear to be being populated in Mac
Word. It's there (otherwise it would error) but it's just not populated.

Worse: If you declare a variable, it is not populated by a Replace
operation. In the PC, it is. The use of "selection" seems to lead to
results close to random. The following works (I tested it in Mac Word on a
177-page document with 89,000 replacements...)

Sub Test()
Dim bWasFound As Boolean
Dim myRange As Range

Set myRange = ActiveDocument.Content

With myRange.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue

Do
.Execute Replace:=wdReplaceAll
bWasFound = .Execute
Loop While bWasFound
End With
End Sub

There's some fairly advanced programming going on in here... Here's the
code again with comments:

Sub Test()
Dim bWasFound As Boolean ' a True/False variable to hold the search result
Dim myRange As Range ' Similar to a selection, but invisible and doesn't
change with editing

Set myRange = ActiveDocument.Content ' All the text in the document

With myRange.Find ' Same as selection, but it won't move
.ClearFormatting ' This is recorded as an afterthought: here is
normal
.Replacement.ClearFormatting
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue ' If you hit the end of document keep going

Do ' A post-test loop!
.Execute Replace:=wdReplaceAll ' We'd like to get the result
here, but it will always be false
bWasFound = .Execute ' Run just the find to get a result
Loop While bWasFound ' Keep at it until result is false
End With
End Sub

OK, the only serious bits are the use of a "Range" instead of a selection,
and the post-test loop.

Ranges are exactly the same as a selection, the only difference is that they
do not appear on screen, and you can have as many of them as you like. But
the important attribute we're using is that a Range does not move after you
edit its content. A selection does.

One of the irritating habits of the Find mechanism is that when it runs, it
redefines the selection. This does not matter if you're using it with
"Replace" and "wdFindContinue" because if Word does not find what it's
looking for inside the selection, it will continue with the whole document.

However, if we try to return the result of the .Execute Replace, it's coming
back empty. So the test will always fail. So we get nasty: we run a
Replace All, then we immediately run a Find. The Find on its own WILL
return a result: True if it found something, False if it didn't.

So here:
Do
.Execute Replace:=wdReplaceAll
bWasFound = .Execute
Loop While bWasFound

We have firstly turned the code upside down by using a "Post Test Loop".
Obviously, there are two kinds: the Pre-test loop which tests the variable
before entering the loop, then keeps running until your exit condition
becomes true, and the Post-test loop, which always does one iteration, then
looks to see if it should keep going.

This is the more elegant way of dealing with the situation I used a kludge
for the other day :) The loop will always execute once, and then keep
going until the find doesn't find anything.

Secondly, we add our own variable to store the result because, there's a bug
in Word that's not populating the official one.

bWasFound = .Execute simply returns the "result" of the Find method to a
variable so we can test it later to see if it actually found anything. If
it did, we loop back and execute another Replace All. Keep doing it until
the Find finds nothing.

So there you go. What should have been an extremely simple problem turned
into a marathon because of two bugs in Word Mac that are not there on the
PC. Welcome to cross-platform programming :) We should not have had to
get our hands this dirty to accomplish what you wanted.

I'll give you one more piece of sage advice: If you wish to solve a problem
like this on a 200-page document with 90,000 replacements and Virtual PC
running, do not use an iBook on your lap. Not if you ever want to start a
family! MacBook Pros are not the only thing that risk cooking a very
sensitive part of your anatomy when they're working hard...

Thanks for the great question!

John,

OK, thanks for the additional information. This is helping me learn a lot.

Now, unfortunately, I have to return to my original question because after
using the code you suggested the looping process in the macro still doesn't
work in Mac Word 2004, though it works fine in PC Word 2003. (By the way, in
case it's relevant, I'm using ver. 11.2 of Mac Word 2004 and Mac OS X 10.4.7
on a 2.1 GHz iMac Power PC G5 with 1 GB Ram.)

To simplify things, I did a test macro with basically just the code you
suggested, as follows:

Sub Test()

Selection.HomeKey Unit:=wdStory
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
Do While .Found
.Execute Replace:=wdReplaceAll
Loop
End With
End Sub

I then did a short Word document with a couple of sentences with multiple
spaces between some of the words. In Mac Word, the test macro seems to run OK
(no error messages) but it does not eliminate all the double spaces. On my PC
running Word 2003, the test macro works perfectly.

So, I'm baffled! I tried moving the While condition from the Do to the Loop
statement, but that didn't help. Is this problem a result of differences
between PC and Mac VBA? It seems hard to believe that basic Find and Replace
operations would be handled differently.

My apologies for pestering you further with this.

Thanks for your help.

--Eric

John McGhie [MVP - Word and Word Macinto said:
Hi Eric:

The exam is a fun challenge! Unfortuntely, there's no way that I could give
a decent answer without further reading & study. Maybe I can come up with a
"passing" answer in the near future.

There are two other people in this group who I *know* know the answer. I am
surprised that the others haven't at least had a go at it. Chickens!!
It's hard to imagine being able to write macros from scratch, however, as
compared to modifying an example you get from somewhere else.

NOBODY does that :) (Well, I do, occasionally, but only for very simple
stuff...). Not only is stealing code the best way to look like a hero, it's
also the only way to get the project completed and tested on time!

Also: The Mac VBA Editor is a savagely-castrated shadow of the one in Word
2003. I don't think ANYONE who has to write much in the way of VBA would
even think of attempting it in Mac Word. I either Remote Desktop Connection
into my PC, if it's running, or fire up Virtual PC and Word 2003. I get the
code running and stable and debugged before I paste it into Mac Word 2004
for final testing.

The extra smarts in the Word 2003 VBE and the extra information in the Word
2003 Help just save you weeks writing anything substantial. For example, in
the PC version, you can stop the action at any specific statement and read
the contents of all of the properties, variables and objects in the macro as
they are at that point. You can't DO that on the Mac, it doesn't support
the required view.
The list of
possible objects, methods, properties, etc., seems very long--not clear
which
ones really apply to a given problem. I guess it just takes time and
concentrated effort to learn.

Nope: You begin by "Recording" what you are trying to do :) Then all you
have to do is add the IF statements and branching and control logic. But
let the Macro Recorder write the bulk of the code for you. It will save you
looking up all those things: it automatically uses the correct things.
Furthermore: VBA is an evolving language that has grown up over time. It's
riddled with inconsistencies and workarounds. There are some things that
plain don't work, even though they should. If you use the macro recorder,
it will automatically record "the one that works" if there are choices. And
sometimes (for example, when you switch into or out of the Header and Footer
View) it substitutes some pre-written VBA code it has stored because it
KNOWS that the correct properties simply don't work...
If I haven't exhausted your patience, can I ask a further question about the
specific macro I was working on? In the version I had for PC Word, I had a
section of code that I got from an article by Dave Rado on how to delete
empty paragraphs, which contained the following:

With Selection.Find
.Text = "^13{2,}"
.Replacement.Text = "^p^p"
(etc.)

It's a bug. The pattern matching specification doesn't work with the ANSI
code specification. Use:

Sub Macro1()
'
' Macro1 Macro
' Macro recorded 3/9/06 by John McGhie
'
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "^p^p"
.Replacement.Text = "^p"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
End Sub


Cheers
:

Hi Eric:

On 2/9/06 1:12 PM, in article
(e-mail address removed), "Eric55"

Wow, many thanks! This was a much more helpful reponse than I had hoped
for.
I have only a basic knowledge of programming, and virtually none in using
VBA, so I have just tried to put a few macros together by piecing together
and modifying things I see on-line, but with only a weak understanding of
what's going on!

That's the *approved* procedure for obtaining success in VBA :) How do
you
think *we* learned it. Oh, OK, there are those extremists who went out and
bought books on the subject and attended university and stuff. But they're
geeks...

Let me ask a follow-up question to help advance my understanding a bit.
Related to my problem of not executing the Find operation before testing
the
Found variable: in the simplified code that you suggested at the end of
your
post, where does the Find operation execute? The "execute" seems to be
associated with the Replace operation. Thanks for your help.

It's not, but you have not understood "dot-languages", "properties",
"inheritance", or "context". :)

You could thinks of the "dots" in the statement as "apostrophe esses". So
now we can read " Selection.Find.Execute " as "The Selection's Find's
Execute command".

VBA and AppleScript are both "object-oriented" languages. This means that
they are based upon "things" (objects). These objects are often physical
things we could see in a document if we looked (although they don't have to
be).

A document is an object, which contains objects, some of which contain
objects and so on. Like Chinese Eggs: big objects contain small objects
which contain smaller objects.

Objects have "properties" and "methods". Properties are like
"characteristics". My shirt is an object, it has a property of Size and a
property of Colour. The Colour property has a property of "Yellow" and (to
you) the size is unknown.

A "method" is like an "ability", literally "the ability to do something."

Let's look at the original statement:

Selection.Find.Execute Replace:=wdReplaceAll

To be strictly semantically correct, we should really write that as:
World.computer.OperatingSystem.Application.Documents.ActiveDocument.
Selection.Find.Execute

However, we can indulge in a little assumption in the interests of brevity.
We can assume that the whole world will not let us play with its computers,
so the computer in question can be implied to be "the computer upon which
our program is executing. Similarly, we can assume that there can be only
operating system controlling this computer.

"Application" is going to be tricky: there could be several. We should
really name it, but even that wouldn't do us much good because there may be
more than one copy running.

Instead, we rely on the rules of "Context". "Context" tells us to "use the
closest one we find to where we are standing." If we are standing in the
room in which I work, the context is "my office" and "the door" thus needs
to further information because there is only one door to my office. If we
were standing in the corridor, we would have to say "which" door.

Since, in VBA, we are running within the context of a single application,
we
do not have to say which one we mean unless we mean one that is outside of
where we are running. So in this case, VBA knows that the "Application" in
question is "this copy of Word".

Now pay attention: this bit gets a little obtuse...

Word can have multiple documents open. So in VBA we need to say which
document we mean. However, because we're lazy, we can allow that to
default
to "the active document" (which is ALWAYS the document containing the
selection). We are relying on the fact that Word can have only ONE
selection, and that the document that contains it must be the active
document, and that these two properties will default, so we do not have to
say "which" document or which selection because there can be only one
selection, and if we find that we automatically know which document we're
talking about.

The "Selection" is an object. It has a method: "Find" (it knows how to
find
things). So once we have narrowed the context down to the selection, we
can
then refer to "Find" unambiguously, because there can be only one selection
and thus only one Find.

So we're now several layers deep into this puzzle. However, because there
is no chance of ambiguity, we do not have to qualify anything yet. Because
there is no chance of any alternatives, we can use some smarts built in to
the VBA compiler to save ourselves a lot of typing.

The first thing we can do is explicitly nominate our context. Because our
whole code block refers to the same context, we can nominate it up front:
"With Selection.Find" All the following statements up to the "End With"
will now operate only within the explicit context described above -- the
selection in the active document.

So now we can remove the qualifying context from each statement: instead of
having to write "Selection.Find" in front of each statement, we can write
it
once and have it apply to the entire block.

Once we do that, we can use a leading dot to IMPLY the context. That's
what
I am doing when I write:
Do While .Found
.Execute Replace:=wdReplaceAll
Loop

The dot in front of the word .Found implies the current context so that bit
means "Do while Selection's Find's Found property (exists)." I could
equally have written "Do While Selection.Find.Found = True". But I know I
can rely on another behaviour of VBA: if I do not qualify a "While"
statement with an operator and an operand, the compiler's default is to
check for "True", and since I know the built-in control variable "Found" is
Boolean, I know it can only be one or the other, true or false.

The dot in front of Execute simply implies the context again: "Selection's
Find's Execute method".

There's nothing in the rules to say that an Object can not contain other
objects. So the Find object can (and does...) contain a Replacement
object.
As soon as you name the Find object as being in your context, the compiler
expects you to do something with the Replacement object. We could, of
course, choose not to use it, but we don't.

Instead, we fire the "Execute" method of the Find object, and set its
"Replace" property to "wdReplaceAll" (change each instance that Find
finds).

Now we have to avoid getting confused by the "replace all". It means
"replace all the instances that THIS iteration of the Find method found.
As
we know, if you have four spaces, and you search for two spaces and change
to one, the first two spaces will be turned into one space, but then the
find will inspect the text FOLLOWING the replacement. It will change those
two into one also. But you will then be left with two spaces where you
previously had four. So we need to run the Find at least one more time to
find them.

In our case, we choose to keep running the Find command until it doesn't
find anything. Each time we run it, it will inspect all of the text in the
document (because we set Wrap to 'wdFindContinue'). When it doesn't find
anything, Find will return with the control variable Found set to False and
our macro then stops trying and exits.

So: the implied question is "should we use shorthand in code?" There is no
"correct" answer. Each individual case is an artistic, not scientific,
choice.

So long as our code is unambiguous, the compiler will produce exactly the
same binary each time. So the question becomes "who else needs to read
this
text." If the answer is "Only Me", and you have a good memory, you can be
as cryptic as you like. If you have lots of experience with VBA and know
the language well, you can save yourself a great deal of typing and
reading.

However, if someone else has to try to work out how to maintain the code
some years later when you're not around or can't remember how it works,
they
will really thank you for letting your text run on a little, leaving a few
more words in there to make it self-explanatory.

Here's an example: I use this as an "exam" for job applicants. If someone
claims to be expert in Word VBA, I show them this and ask them what it
does:

With myRange.Paragraphs
.Style = "Table Number"
With .First.Range.ListFormat
.ApplyListTemplate .ListTemplate, False
End With
With .Last
.KeepWithNext = False
.SpaceAfter = 4
End With
End With

There's a prize for the most correct guess. Those who've seen it before,
be
quiet: let the new students have some fun!

Cheers


--Eric

:

Hi Erik:

You have a "First iteration" problem. *My* code is full of them :)

Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop

This tests the control variable (Found) *before* you have executed the
Find
statement. It will always be False... So your Execute will never be
executed.

You need to kludge it...

Selection.Find.Execute Replace:=wdReplaceAll
Do While Selection.Find.Found
Selection.Find.Execute Replace:=wdReplaceAll
Loop

--

Please reply to the newsgroup to maintain the thread. Please do not email
me unless I ask you to.

John McGhie <[email protected]>
Microsoft MVP, Word and Word for Macintosh. Consultant Technical Writer
Sydney, Australia +61 (0) 4 1209 1410
 
E

Eric55

Hi John:

OK, this is terrific! I'm going to look this over more carefully, but wanted
to thank you as soon as I saw it. You must have spent a LOT of time on this.
I'll report back after I've read and implemented your suggestions.

--Eric

John McGhie [MVP - Word and Word Macinto said:
Hi Eric:

You're not pestering me, you are helping me to indulge my hobby. My hobby
is to answer questions: that's what I come here to do. I leave unsatisfied
if there are not enough questions or they're all boring or from rude people
who are too stupid to look in the Help before asking :) so you're helping
me get my fix :)

Now: Hmmm... I wrote my original in Mac Word, it shoulda worked...

Right!!! Officially NOT HAPPY!!!

There appears to be a NASTY bug in Mac Word. The ".Found" system variable
that is intrinsic to Word 2003 does not appear to be being populated in Mac
Word. It's there (otherwise it would error) but it's just not populated.

Worse: If you declare a variable, it is not populated by a Replace
operation. In the PC, it is. The use of "selection" seems to lead to
results close to random. The following works (I tested it in Mac Word on a
177-page document with 89,000 replacements...)

Sub Test()
Dim bWasFound As Boolean
Dim myRange As Range

Set myRange = ActiveDocument.Content

With myRange.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue

Do
.Execute Replace:=wdReplaceAll
bWasFound = .Execute
Loop While bWasFound
End With
End Sub

There's some fairly advanced programming going on in here... Here's the
code again with comments:

Sub Test()
Dim bWasFound As Boolean ' a True/False variable to hold the search result
Dim myRange As Range ' Similar to a selection, but invisible and doesn't
change with editing

Set myRange = ActiveDocument.Content ' All the text in the document

With myRange.Find ' Same as selection, but it won't move
.ClearFormatting ' This is recorded as an afterthought: here is
normal
.Replacement.ClearFormatting
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue ' If you hit the end of document keep going

Do ' A post-test loop!
.Execute Replace:=wdReplaceAll ' We'd like to get the result
here, but it will always be false
bWasFound = .Execute ' Run just the find to get a result
Loop While bWasFound ' Keep at it until result is false
End With
End Sub

OK, the only serious bits are the use of a "Range" instead of a selection,
and the post-test loop.

Ranges are exactly the same as a selection, the only difference is that they
do not appear on screen, and you can have as many of them as you like. But
the important attribute we're using is that a Range does not move after you
edit its content. A selection does.

One of the irritating habits of the Find mechanism is that when it runs, it
redefines the selection. This does not matter if you're using it with
"Replace" and "wdFindContinue" because if Word does not find what it's
looking for inside the selection, it will continue with the whole document.

However, if we try to return the result of the .Execute Replace, it's coming
back empty. So the test will always fail. So we get nasty: we run a
Replace All, then we immediately run a Find. The Find on its own WILL
return a result: True if it found something, False if it didn't.

So here:
Do
.Execute Replace:=wdReplaceAll
bWasFound = .Execute
Loop While bWasFound

We have firstly turned the code upside down by using a "Post Test Loop".
Obviously, there are two kinds: the Pre-test loop which tests the variable
before entering the loop, then keeps running until your exit condition
becomes true, and the Post-test loop, which always does one iteration, then
looks to see if it should keep going.

This is the more elegant way of dealing with the situation I used a kludge
for the other day :) The loop will always execute once, and then keep
going until the find doesn't find anything.

Secondly, we add our own variable to store the result because, there's a bug
in Word that's not populating the official one.

bWasFound = .Execute simply returns the "result" of the Find method to a
variable so we can test it later to see if it actually found anything. If
it did, we loop back and execute another Replace All. Keep doing it until
the Find finds nothing.

So there you go. What should have been an extremely simple problem turned
into a marathon because of two bugs in Word Mac that are not there on the
PC. Welcome to cross-platform programming :) We should not have had to
get our hands this dirty to accomplish what you wanted.

I'll give you one more piece of sage advice: If you wish to solve a problem
like this on a 200-page document with 90,000 replacements and Virtual PC
running, do not use an iBook on your lap. Not if you ever want to start a
family! MacBook Pros are not the only thing that risk cooking a very
sensitive part of your anatomy when they're working hard...

Thanks for the great question!

John,

OK, thanks for the additional information. This is helping me learn a lot.

Now, unfortunately, I have to return to my original question because after
using the code you suggested the looping process in the macro still doesn't
work in Mac Word 2004, though it works fine in PC Word 2003. (By the way, in
case it's relevant, I'm using ver. 11.2 of Mac Word 2004 and Mac OS X 10.4.7
on a 2.1 GHz iMac Power PC G5 with 1 GB Ram.)

To simplify things, I did a test macro with basically just the code you
suggested, as follows:

Sub Test()

Selection.HomeKey Unit:=wdStory
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = " "
.Replacement.Text = " "
.Forward = True
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
Do While .Found
.Execute Replace:=wdReplaceAll
Loop
End With
End Sub

I then did a short Word document with a couple of sentences with multiple
spaces between some of the words. In Mac Word, the test macro seems to run OK
(no error messages) but it does not eliminate all the double spaces. On my PC
running Word 2003, the test macro works perfectly.

So, I'm baffled! I tried moving the While condition from the Do to the Loop
statement, but that didn't help. Is this problem a result of differences
between PC and Mac VBA? It seems hard to believe that basic Find and Replace
operations would be handled differently.

My apologies for pestering you further with this.

Thanks for your help.

--Eric

John McGhie [MVP - Word and Word Macinto said:
Hi Eric:

On 3/9/06 11:23 AM, in article
(e-mail address removed), "Eric55"

The exam is a fun challenge! Unfortuntely, there's no way that I could give
a decent answer without further reading & study. Maybe I can come up with a
"passing" answer in the near future.

There are two other people in this group who I *know* know the answer. I am
surprised that the others haven't at least had a go at it. Chickens!!

It's hard to imagine being able to write macros from scratch, however, as
compared to modifying an example you get from somewhere else.

NOBODY does that :) (Well, I do, occasionally, but only for very simple
stuff...). Not only is stealing code the best way to look like a hero, it's
also the only way to get the project completed and tested on time!

Also: The Mac VBA Editor is a savagely-castrated shadow of the one in Word
2003. I don't think ANYONE who has to write much in the way of VBA would
even think of attempting it in Mac Word. I either Remote Desktop Connection
into my PC, if it's running, or fire up Virtual PC and Word 2003. I get the
code running and stable and debugged before I paste it into Mac Word 2004
for final testing.

The extra smarts in the Word 2003 VBE and the extra information in the Word
2003 Help just save you weeks writing anything substantial. For example, in
the PC version, you can stop the action at any specific statement and read
the contents of all of the properties, variables and objects in the macro as
they are at that point. You can't DO that on the Mac, it doesn't support
the required view.

The list of
possible objects, methods, properties, etc., seems very long--not clear
which
ones really apply to a given problem. I guess it just takes time and
concentrated effort to learn.

Nope: You begin by "Recording" what you are trying to do :) Then all you
have to do is add the IF statements and branching and control logic. But
let the Macro Recorder write the bulk of the code for you. It will save you
looking up all those things: it automatically uses the correct things.
Furthermore: VBA is an evolving language that has grown up over time. It's
riddled with inconsistencies and workarounds. There are some things that
plain don't work, even though they should. If you use the macro recorder,
it will automatically record "the one that works" if there are choices. And
sometimes (for example, when you switch into or out of the Header and Footer
View) it substitutes some pre-written VBA code it has stored because it
KNOWS that the correct properties simply don't work...

If I haven't exhausted your patience, can I ask a further question about the
specific macro I was working on? In the version I had for PC Word, I had a
section of code that I got from an article by Dave Rado on how to delete
empty paragraphs, which contained the following:

With Selection.Find
.Text = "^13{2,}"
.Replacement.Text = "^p^p"
(etc.)

It's a bug. The pattern matching specification doesn't work with the ANSI
code specification. Use:

Sub Macro1()
'
' Macro1 Macro
' Macro recorded 3/9/06 by John McGhie
'
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "^p^p"
.Replacement.Text = "^p"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
End Sub


Cheers





:

Hi Eric:

On 2/9/06 1:12 PM, in article
(e-mail address removed), "Eric55"

Wow, many thanks! This was a much more helpful reponse than I had hoped
for.
I have only a basic knowledge of programming, and virtually none in using
VBA, so I have just tried to put a few macros together by piecing together
and modifying things I see on-line, but with only a weak understanding of
what's going on!

That's the *approved* procedure for obtaining success in VBA :) How do
you
think *we* learned it. Oh, OK, there are those extremists who went out and
bought books on the subject and attended university and stuff. But they're
geeks...

Let me ask a follow-up question to help advance my understanding a bit.
Related to my problem of not executing the Find operation before testing
the
Found variable: in the simplified code that you suggested at the end of
your
post, where does the Find operation execute? The "execute" seems to be
associated with the Replace operation. Thanks for your help.

It's not, but you have not understood "dot-languages", "properties",
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top