Re: Word 2004 VBA -> Applescript
- From: Paul Berkowitz <berkowit@xxxxxxxxxxxxxxxx>
- Date: Thu, 31 Aug 2006 13:57:56 -0700
On 8/31/06 1:40 AM, in article C11C6D36.197DE%daix@xxxxxxxxxx, "David-Artur
Daix" <daix@xxxxxxxxxx> wrote:
What are the circumstances that you'd need an explicit 'on run' handler with
parameters? Will you be calling this script from outside (RealBasic?) <snip>
I don't know what RB requires.
Yes, that's the way RB wants its compiled script written so that they can be
called from within the RB code just as though they were regular RB functions
using the name of the compiled scripts' _files_ one includes in the project
(in other word what would be the name of the subroutine becomes the name of
the script file, while the code starts with 'on run').
It's annoying, since as far as I can tell you must have one compiled script
per subroutine, even for something as simple as 'save as active document
file name docName'.
That sounds like a reason in itself for trying out AppleScript Studio. You
can put everything in one script file if you like, although it is
recommended that you use an MVC (Model-View-Controller) structure for long
scripts and bugger projects. You can put your UI event-driven handlers in
the main script file and call out to "action" handlers in another script
file, for example. Or not. It's up0 to you. The full panoply of AppleScript
script objects and handlers (subroutines) are available to you as you like.
I suspect that with VBA gone from Office, all the REALbasic Office
Automation features will stop working as well, since they're just a way to
call upon VBA inside the Office Suite using OLE. So that every command,
object and constant I now use that rely on Word to do the work, even those
that are not unbearably slow (OLE is not as efficient as AS on the Mac, to
say the least), but just simple save or screen update operations, will have
to be rewritten as small snippets of "on run" AS code inside compiled
scripts.
We don't know at this point whether RealBasic's hooks into OLE will just
work "as is" on Intel Macs, or if RB will have to do a conversion similar to
what MS has declined to do for VBA. The reason why AppleScript just works
"as is" is because Apple has built the low-level changes into AppleScript
itself (and built the application conversion into Xcode's "Universal Binary"
checkbox). But Apple knew about the Intel changes for years and could plan
ahead.
I'll have a better idea of what I need to do when the next version of Office
is available. Right now I'm just trying to get familiar with AppleScript and
what's need to translate VBA code into AS (I generally find the Visual Basic
syntax much easier to use and understand, unfortunately). And whether or not
RB can be used to create the interface and main logic. Right now it appears
so, which would let me use AS only for the work done inside Word itself (the
find & replace "engine", a few saves and screen updates, checking Word's
version, that kind of things: all in all maybe half a dozen rather short
subroutines) and reuse the RB prototype I've created last year for the rest:
a great time saver.
In this case, it _is_ necessary, since the separate stories are not
available as 'every story of active document'. 'story' is not a class,
stories are not elements (child objects) of document. They seem to live in a
weird counter-culture of their own. ;-) There is a an enumeration 'story
type' used as a parameter to the 'get story range' command. For each story
type you specify, you get a different text range as a result, and you can
then run your 'tell StoryRangeFind' procedure on each range. Because you
have to specify each story type by name, you cannot run a repeat loop on a
list (such as 'every story of theDocument', if story _were_ an element).
Instead having to re-write the whole business 10 times over, call it as a
separate subroutine 10 times:
That's the conclusion I had reached. So I have tried to create a list of the
available story ranges myself, so that I could then use 'repeat with' on it,
like this:
-- List declaration.
set StoryRangeList to {}
-- List reference declaration.
set StoryRangeListRef to a reference to StoryRangeList
Not necessary.
try
set MainTextStoryRange to get story range active document story type
main text story
copy MainTextStoryRange to the end of StoryRangeListRef
set FootnotesStoryRange to get story range active document story type
footnotes story
copy FootnotesStoryRange to the end of StoryRangeListRef
set EndnotesStoryRange to get story range active document story type
endnotes story
copy EndnotesStoryRange to the end of StoryRangeListRef
set CommentsStoryRange to get story range active document story type
comments story
copy CommentsStoryRange to the end of StoryRangeListRef
[etc.]
end try
repeat with StoryRange in StoryRangeListRef
end repeat
Yes. When you read the Neuburg book, you'll learn that it's a lot more
efficient to write:
set end of StoryRangeList to MainTextStoryRange
rather than
copy MainTextStoryRange to the end of StoryRangeListRef
but otherwise, yes, that will be fine. ('set end' doesn't have to keep
copying the entire list over and over internally.) Then you can operate on
the list in a repeat loop.
If and when you have a very large list (say 100 items or more, but
especially for thousands) it then becomes faster to use 'a reference to' -
actually there are better techniques than that (use 'my listName' in
top-level scripts and run handlers, make a script object within regular
handlers - these are advanced techniques). But for a list of 11 items, this
is really not necessary nor helpful.
But it doesn't work, because 'get story range' doesn't produce the expected
result.
[Update: I've now read your following posts and see that you have run into
the same problems as I have with that command and its documentation: thank
you for testing it and letting me know I'm not crazy! I just couldn't
understand why my code wouldn't work, even though I ended up simply copying
and pasting the examples given in the documentation to replace my own code.
And of course being new to AS I thought it was my fault.]
It's a good idea to test out just getting a single example as I did, to see
what results. Yes, the reason it didn't work for you is due to the bad bug
that 'get story range' produces useless dummy ranges.
[Update: there is a typo in the Word AS Reference, p. 271: in the story type
list for the 'get story range' command, one can read "even pages header
footer story" instead of the expected "even pages footer story" ("header"
there is a copy/paste mistake I assume).]
Right. Thanks for spotting that. I'll pass that on.
Yes, see above. But don't call it a 'collection'. Lists in AppleScript are
It is a nuisance that 'story' is merely an enumerated parameter to a command
'get story range;' rather than part of the object model as StoryRange is in
VBA. My guess would be that because there is no such thing as a Collection
Object in AppleScript, there's nothing like VBA's StoryRanges collection
Indeed! It's quite annoying. Yet can't one create one's own "collections" by
using AS lists, as I intended to do?
like arrays in VB, not like those odd 'Collection Objects' they have there.
It is my understanding that by omitting the 'on error' part of the 'try'
statement I will be able to simply ignore all the cases where the story
range does not in fact contain any data. That way I will end up with a list
of all the existing story ranges without actually encountering any errors.
Is that correct?
Yes. The reason I suggested using that 'checkStory' variable (set to true or
false) is because that way you can trap _just_ the 'get story range' command
in a try/error block. You don't want to include the call to the
ProcessStoryRange() handler in the same try/error block, of course, because
that would prevent you from identifying other errors. using your own
structure of setting the end of StoryRangeList to 'get story range' means
you can put that command in the try/error block without needing to bother
with the checkStory variable, and will cut out extra lines.
One thing I have not checked out yet is that in a document with several
sections, each section has its own headers and footers of all the different
flavors - perhaps its own footnotes and endnotes too. Yet 'get story range'
takes only document - not section - as its direct object. I wonder if it
only gets the first section's results that way, or somehow gets the whole
document's.
By reading your following posts, I see that you've looked into this matter
yourself: thanks again for your help! And for providing me with examples of
how to use the 'next story range' property, which was not clear to me when
reading the Word AS Reference (more examples are needed in it I'd say).
But it's all moot at the moment... I'll try to make sure they get this bit
right when they fix the bug.
Thank you. In fact maybe you could suggest they implement in any way they
choose an elegant AS solution (indeed at first I expected something like
'repeat with every story range of active document' or even 'repeat with
StoryRange in (get story ranges)' to work: you can imagine my
disappointment!) to obtain the same result as the simple VB instruction:
'For Each StoryRange In ActiveDocument.StoryRanges'
No, that cannot be done, for the reasons I explained earlier. 'story range'
is not a type of object - they're just ranges. Even in VBA, the StoryRange
is not a regular type of Object, nor is StoryRanges even a regular sort of
Collection Object - you cannot use the Add Method on it. Well, VBA can make
up ad hoc rules like that - they own the language. In AppleScript, an
application cannot legislate such that you can't 'make new story range at
active document'. It can't be done. Furthermore the restricted set of story
range types (main text, primary footer, etc. etc.) can only be specified as
an enumeration. and tat means it has to be a parameter to a command ('get
story range [story type]'. So it has to be the way it is.
The idea of having not only to go through each story range of the first
section, but also of all the next subsequent sections instead of having Word
do the work for me makes me shudder.
Once it's working properly doing
repeat while next story range of storyRange is not missing value
my ProcessStoryRange(storyRange)
end repeat
will work just fine.
If only Word all by itself could create a complete list of the available
story ranges for the user, that would be really a good improvement.
"all by itself"?
That way it would become simple to script "replace all" operations that
actually apply to the whole document, not just the main section.
One more question. In my original VB code, in the find & replace operation,
I take into account the font color, so that normally it doesn't change
(using the "wdColorAutomatic" constant). But sometimes I want to change it
and color the replacement characters in red (to note characters that can't
be transcoded for instance, because they do not exist in the target
font/encoding).
When calling the script from REALbasic, I must pass the font object's 'color
index' value that I want to use as a string (VBA constants are no longer an
option and REALbasic can't handle the equivalent AS values directly). But
when I need to set that property in the script, a string won't do. In other
word, if 'fontColor' is the passed parameter, this snippet does not work:
set color index of font object of replacement of StoryRangeFind to fontColor
What I've come up with is this:
if fontColor = "red" then
set color index of font object of replacement of StoryRangeFind to red
else
set color index of font object of replacement of StoryRangeFind to auto
end if
Is there a better way to handle that situation?
Once again, this is an enumeration (of '[background pattern] color index'
type). Naturally you can't the color index of a font to a string. Yes,
that's as good a way as any, but you'd need a subroutine that iterated
(if/else if) through all 18 color indices. Here's another way:
set fontColor to "red"
set color index of font object of replacement of StoryRangeFind to (run
script fontColor)
'run script' scripting addition (in Standard Additions, in Script
Editor/File/Open Dictionary - Standard Additions is built into every Mac and
always available) takes a string (or a compiled script's file path). So if
you give it a string inside an application's tell block and it can find the
string as a keyword in that app's dictionary it will compile it on the spot.
If you know that you will always be feeding it a valid enumeration, you can
use a variable like fontColor here to represent the string and do it in one
line instead of 18 if/else iterations. do note, however, that although the
18 if/else iterations take up more space in your script, it will run MUCH
faster than 'run script'. That won't matter if you're just calling it a few
times. But if it were running in a repeat loop on 1000 repeats. say. you'd
be much MUCH faster the "long" way round. 'run script', like every scripting
addition, carries a certain overhead.
Best,
--
Paul Berkowitz
MVP MacOffice
Entourage FAQ Page: <http://www.entourage.mvps.org/faq/index.html>
AppleScripts for Entourage: <http://macscripter.net/scriptbuilders/>
Please "Reply To Newsgroup" to reply to this message. Emails will be
ignored.
PLEASE always state which version of Microsoft Office you are using -
**2004**, X or 2001. It's often impossible to answer your questions
otherwise.
.
- Follow-Ups:
- Re: Word 2004 VBA -> Applescript
- From: David-Artur Daix
- Re: Word 2004 VBA -> Applescript
- From: Elliott Roper
- Re: Word 2004 VBA -> Applescript
- Next by Date: Re: Word 2004 VBA -> Applescript
- Next by thread: Re: Word 2004 VBA -> Applescript
- Index(es):