Backwards Macro

R

Rebecca

Greetings.

I am using MS Word XP, and I have a problem I do not have
the ability to solve. I have many paragraphs of words,
three of which I will produce below:

house flower tree box candy bug book
cheese rat dog bird mouse flea
leaf grass skunk telephone computer blue

etc., etc.

What I want to do is have a macro take the last word in
each row and put them in one column, as follows:

book (from top row, last word)
bug
candy
box
tree
flower
house
flea (from second row, last word)
mouse, etc., etc.

Could someone please explain how I can create a macro to
do this? But please explain everything in very easy
English, step by step, because I am a beginner.

Thanks.
 
J

Jay Freedman

Hi, Rebecca,

Forgive me if I'm wrong, but this task sounds like the kind of useless logic
puzzle handed out as homework in a programming class. I usually try to avoid
giving full answers to homework, since the benefit to the student is in
trying to do it, not in having the answer. This time I'll make an exception,
in case I'm wrong and because the problem has some interesting implications.

VBA has a lot of useful collections such as Paragraphs and Words, and most
such collections have useful properties .First and .Last that are much
faster and easier than using an index number. The way this problem is
structured, though, those properties aren't of much use. Similarly, VBA has
a StrReverse function, but it reverses the order of all the characters in a
string, not the order of the words. The elegant tools are of little use, and
what's left is pretty much brute force.

The general idea is to build up a string, one word at a time, starting with
the last word in a paragraph and ending with the first, and separating them
with paragraph marks along the way. When the string is complete, the
original paragraph is replaced with the string's contents.

If you tried to do this starting with the first paragraph, you'd find that
what was originally paragraph 2 has become something like paragraph 10 --
depending on how many words were in the first paragraph. The solution is to
process the paragraphs in reverse order; that way, paragraph 2 remains
paragraph 2 until you get there.

The following macro contains enough comments that you should be able to see
how it works. If you copy it and paste it into the VBA editor, you can put
the cursor on any keyword and press F1 to get the Help topic that explains
it.

Sub Backwards()
Dim nPara As Long, nWord As Long
Dim CurrPara As Paragraph
Dim ParaWords As String

' Work from last paragraph to first.
' This prevents the unprocessed paragraphs'
' index numbers changing as work proceeds.
For nPara = ActiveDocument.Paragraphs.Count To 1 Step -1

' Make an object contain the current paragraph.
Set CurrPara = ActiveDocument.Paragraphs(nPara)

' Initialize the "holder".
ParaWords = ""

' Work from the last word in the paragraph to first.
For nWord = CurrPara.Range.Words.Count To 1 Step -1

' Don't add empty paragraphs.
If CurrPara.Range.Words(nWord) <> vbCr Then

' Attach the current word and a paragraph mark
' to the end of the holder. The Trim function
' gets rid of spaces.
ParaWords = ParaWords & _
Trim(CurrPara.Range.Words(nWord)) & vbCr
End If
Next nWord
' At the end of the loop, the holder contains the
' words of the current paragraph in reverse order,
' separated by paragraph marks.

' Replace the current paragraph with the
' contents of the holder.
CurrPara.Range.Text = ParaWords
Next nPara
End Sub
 
K

Klaus Linke

house flower tree box candy bug book
cheese rat dog bird mouse flea
leaf grass skunk telephone computer blue

etc., etc.

What I want to do is have a macro take the last word in
each row and put them in one column, as follows:

book (from top row, last word)
bug
candy
box
tree
flower
house
flea (from second row, last word)
mouse, etc., etc.



Hi Rebecca,

Not too easy...

If you don't need to keep any formatting, the best (= fastest) way would be
to use string functions like Left, Mid, Instr, Split, Join.
But those are a bit difficult if you have never used any of them.
And it wouldn't have much to do with Word, apart from the start where you
would read the text into a string and delete it, and the end where you
would insert the end result into Word again.

The next best thing would be to use a Range, and move that around to access
different parts of the text, and move them. But this also is a bit
difficult, because you can't easily see what is happening.

So let's do it with the Selection.

Let us start by arranging the document and the VBA editor next to each
other.
Create an empty macro in the editor, and give it a name that describes what
you try to do.

I used

Sub ParagraphsSplitWordsBackwards()
End Sub

But if you can think of a better name, use that!

Put your cursor in the first paragraph in the document.

Let's first try to select the whole paragraph.
You can do that with
Selection.Paragraphs(1).Range.Select
in the macro.
Type that into the macro, and run the macro (F5, or single-step through the
code with the F8 key).

If the Selection would span several paragraphs, Selection.Paragraphs(1)
would be the first Paragraph in the Selection, Selection.Paragraphs(2) the
second, and so on.

If the Selection is inside a paragraph, Selection.Paragraphs(1) gives you
this paragraph. That takes a bit of getting used to, but is very useful.

Now if we want to select that paragraph,
Selection.Paragraphs(1).Select
might seem logical, but after you type
Selection.Paragraphs(1).
"Select" isn't offered. (I assume you have checked all the help you can get
in the VBA editor's "Tools > Options > Editor"?)

But just about every object in Word has a Range, which can be selected, so
we use the roundabout way of first getting the Range of the paragraph.
Selection.Paragraphs(1).Range.Select

Now let's try to get the last Word in the Selection.
Selection.Words.Last.Select

You may be in for a surprise here: The final paragraph mark gets selected.
Word treats a paragraph mark (and punctuation like "." or ",") as separate
words.

We can try to select the last word with
Selection.MoveStart Unit:=wdWord, Count:=-1

You notice the yellow arrow to the left of the code if you use F8 to
single-step through the macro?
You can drag that arrow to any line in the code with the mouse, and that
line gets executed next with F8 (single step).
Also you can type new lines into your macro, and drag the yellow arrow to
them.

In the document window, you can use Ctrl+Z (Undo) to undo any changes if
the macro didn't work as you expected.

Now we want to move the selected last Word into an extra paragraph above
the paragraph with all the rest of the words.
Let's first insert a paragraph mark in front of the word:
Selection.InsertBefore vbCr

Now the word is in a paragraph by itself, but below. And the paragraph mark
above is still selected.
First, deselect that pagaraph mark:
Selection.MoveStart Unit:=wdCharacter, Count:=1
The paragraph with the single word is now selected.

One way to move it up would be to cut it to the clipboard, move the
Selection up, and paste it again.

A way that needs only one line of code is
Selection.Range.Relocate wdRelocateUp

I use this all the time from the user interface (Alt+Shift+Up,
Alt+Shift+Down) to move one or several paragraphs or table rows up or down.
If you know such tricks from the user interface, you can use the macro
recorder to give you the code.

Now we have moved the last word up above the original paragraph.
But it's still selected. To re-select the original paragraph, let's use
Selection.Next(Unit:=wdParagraph, Count:=1).Select

(You can also try some other ways to select it. With two or more lines of
code, there are lots of possibilities. You can experiment with all the
methods you get offered after you type "Selection" followed by a ".")

The code up to now is
Selection.Paragraphs(1).Range.Select
Selection.Words.Last.Select
Selection.MoveStart Unit:=wdWord, Count:=-1
Selection.InsertBefore vbCr
Selection.MoveStart Unit:=wdCharacter, Count:=1
Selection.Range.Relocate wdRelocateUp
Selection.Next(Unit:=wdParagraph, Count:=1).Select

We need to repeat this for the rest of the words. We should stop if there
are only 2 words left (one real word, and the paragraph mark):

Selection.Paragraphs(1).Range.Select
Do While Selection.Words.Count > 2
Selection.Words.Last.Select
Selection.MoveStart Unit:=wdWord, Count:=-1
Selection.InsertBefore vbCr
Selection.MoveStart Unit:=wdCharacter, Count:=1
Selection.Range.Relocate wdRelocateUp
Selection.Next(Unit:=wdParagraph, Count:=1).Select
Loop

Putting the condition at the top of the loop makes sure that the loop
doesn't get executed at all if it is not necessary.

I left the first line (which originally selected the paragraph) outside the
loop, because the paragraph already is selected.

After all the words in the first paragraph have been processed, we need to
move the Selection down a paragraph again:
Selection.Next(Unit:=wdParagraph, Count:=1).Select
and start over with the next paragraph.

We need to do this in a loop until the next paragraph is empty or contains
only one word, at which point the macro is done:

Sub ParagraphsSplitWordsBackwards()
Selection.Paragraphs(1).Range.Select
Do
Do While Selection.Words.Count > 2
Selection.Words.Last.Select
Selection.MoveStart Unit:=wdWord, Count:=-1
Selection.InsertBefore vbCr
Selection.MoveStart Unit:=wdCharacter, Count:=1
Selection.Range.Relocate wdRelocateUp
Selection.Next(Unit:=wdParagraph, Count:=1).Select
Loop
Selection.Next(Unit:=wdParagraph, Count:=1).Select
Loop Until Selection.Words.Count <= 2
End Sub

This time we put the condition at the end, since we want to run the inner
loop at least once.
If the word count is 2 or less, nothing bad will happen because of the
condition in the inner loop.

If you got lost somewhere, just put your cursor in the finished macro, and
use F8 to single-step through the code, then watch what every line of code
does.

You can try some different methods (there are lots of possibilities to move
the Selection), or you could try to work with a Range:
Dim myRange as Range
Set myRange = Selection.Paragraphs(1).Range

Ranges don't have as many methods for movement, so you'd have to rewrite
the code quite a bit.

To watch were myRange is at any given time, you can use
myRange.Select
in your code (or in the "immediate window").

Instead of .Relocate, you could try .Cut and .Paste, or use .FormattedText
to duplicate the Range where you want to move it, and .Delete the old
Range.

Or you could try some totally different approach: For example replace all
spaces in a paragraph with ^p paragraph marks. The words are now in
paragraphs by themselves, but in the wrong order. Put them in a one-column
table, put numbers 1, 2, 3 ... in a second column, sort the table so the
numbers are descending, remove the numbered column, and convert the table
back to text.

Hope this has given you some ideas,
Klaus
 
R

Rebecca

Thanks Jay (and Klaus, too), but no, this was NOT a
homework assignment. It is an English interlinear of the
Hebrew Bible (though the words I used as examples are
different, of course), and, as you may know, judging from
your name, Hebrew flows from right to left, adding all
kinds of problems.
 

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