Question for Paul and other AppleScripters

R

Robert Barrimond

I'm new to AppleScript and have been enjoying the learning experience,
however, I've run into a frustrating stumbling block.

First the context. I'm writing a script to do batch edits on my contacts.
For example, I'd like to use some of the custom fields and need to a
convenient way to edit these values for many, many contacts at one time.

Now the problem. I can't seem to programmatically inspect either the
contact objects or use a reference to to loop through the list of selected
contacts to edit the property the user chooses by a "choose from list" of
strings. My first strategy was to try to access the properties of the
contact objects by converting the user chosen string into a property label
for each selected contact. I couldn't find a way to do this. My second
strategy was to use a reference to create a reference that I would loop
through the contacts and then access the properties of each contact
indirectly. I also failed to make that happen. Either of these I had hoped
would allow me to write a subroutine to handle the actual looping and
editing. This would be trivial in Java, I hope AppleScript isn't forcing me
to commit the Greatest Programming Sin of All: repeat code that should be a
method/function/subroutine.
 
P

Paul Berkowitz

I'm new to AppleScript and have been enjoying the learning experience,
however, I've run into a frustrating stumbling block.

First the context. I'm writing a script to do batch edits on my contacts.
For example, I'd like to use some of the custom fields and need to a
convenient way to edit these values for many, many contacts at one time.

Now the problem. I can't seem to programmatically inspect either the contact
objects or use a reference to to loop through the list of selected contacts to
edit the property the user chooses by a "choose from list" of strings. My
first strategy was to try to access the properties of the contact objects by
converting the user chosen string into a property label for each selected
contact. I couldn't find a way to do this. My second strategy was to use a
reference to create a reference that I would loop through the contacts and
then access the properties of each contact indirectly. I also failed to make
that happen. Either of these I had hoped would allow me to write a subroutine
to handle the actual looping and editing. This would be trivial in Java, I
hope AppleScript isn't forcing me to commit the Greatest Programming Sin of
All: repeat code that should be a method/function/subroutine.

First of all, you'd better forget what you learned in other programming
languages. If you want to use AppleScript, you have to learn AppleScript.
The best way to do that - especially for people with some programming
experience - is to to buy "AppleScript: The Definitive Guide" by Matt
Neuburg, published by O'Reilly.

AppleScript actually has more powerful commands for setting a particular
property of many objects of the same class, and even for getting a filtered
set of these by 'whose' clause. But setting the property to all those
objects at once only works if the value for every object will be the same.
Otherwise, if the value will be dependent on some other property of the
contact, for example, you do need to use a loop. There are ways to speed up
to traversal of a list in a repeat loop, although the main slowdown here
will be the AppleEvents and not the list traversal. The speed-up process has
to be done differently in a subroutine than at the top level of a script, by
the way, 'a reference to', or - even faster 'my
  • ' - only works for
    top-level variables, i.e. globals (whether explicit or implicit) and
    properties. Read Matt's book for full details on implicit global-ness of
    top-level variables. The alternate method in a subroutine is not well-known,
    but among those who do it's called the "Serge script object" method. I can
    explain it to you.

    However, I think you have a few other questions that precede that. I can't
    actually quite make out what you're asking. Could you try in plainer
    English, using examples, and skipping the asides mocking AppleScript and
    other mutterings? I'll give it a go anyway, but I'll really not clear on
    what you're trying to do.

    HOW do want to "inspect" the contact objects and/or how do you propose to
    select them?

    Nothing could be easier than getting every contact:

    set theContacts to every contact

    ['contact' is an element of the application, which is not therefore
    specified. It is implicit. No larger container is needed in the command.]

    Or, you could filter just the particular contacts in which you may have an
    interest:

    set companyContacts to every contact whose company ‚ ""

    'whose' filtering can combine properties by logical and and orb:

    set myContacts to every contact whose company ‚ "" and name = ""
    -- name is mapped from the 'by name' of the element and is same as
    'display name' property


    set myContacts to every contact whose company ‚ "" and (custom field one
    = "" or custom field two = "" or custom field three = "")


    and so on. With this sort of selection you might even be able to do your
    whole script in one fell swoop:


    set custom field one of every contact where its category contains
    {category "Baseball"} and its custom field one = "" to "Red Sox"

    **but you can't do

    set custom field one of every contact where its category contains
    {category "Baseball"} and its custom field one = "" to (its company & ": Red
    Sox")

    For that, you'd need a repeat loop. See below.


    Alternately, you could get the selection directly from whatever the user
    selected (highlighted) in the Address Book, which is a list. You should
    include error checks for no selection, and for the class of selection as
    list (rather than text or folder) and for the class of each item as you loop
    later (in custom views you may have mixed classes of objects), but, omitting
    that:

    set myContacts to selection

    Here you have to use a loop since the AppleScript language does not (still)
    enable whose clauses on lists.


    I can't quite imagine the circumstances where you'd want the user to choose
    the properties from a string 'choose form list' - mostly because the
    AppleScript terms for many of of the properties (e.g. 'description') does
    not correspond to what the user is familiar with in the UI ('Notes').
    Instead, I would recommend simply constructing your own list of
    user-friendly terms and dealing with the properties in an if/else if
    structure:

    set whichItems to choose from list {"First Name", Last Name", "Notes",
    "Custom 1", "Custom 2"...... etc.}
    -- you can get the actual names of the custom fields from 'address book
    1's custom field one name', etc.)

    Then when you do your loop, deal with that on each iteration:

    repeat with i from 1 to (count theContacts)
    set theContact to item i of theContacts -- see below
    if whichItems contains "Custom 1" then
    set custom field one of theContact to (theContact's company & ":
    Red Sox")
    else if whichItems contains "Custom 2" then
    set custom field two of theContact to (theContact's company & ":
    Red Sox")
    --else if ...
    --etc.
    end if
    end repeat



    Now, I don't actually know what you mean by


    My second strategy was to use a reference to create a reference that I
    would loop through the contacts and then access the properties of each
    contact indirectly. I also failed to make that happen. Either of these I
    had hoped would allow me to write a subroutine to handle the actual looping
    and editing.


    There are ways to use 'a reference to' in AppleScript to avoid resolving a
    variable to its value. (In the AppleScript language itself, this only
    happens "built-in" without the 'reference to' for mutable things: members of
    lists and records and for properties of dates and for script objects.) You
    may be able to do what you want.

    Chances are you're getting errors due to other mistakes. Instead of my
    guessing here, you need to provide an example.

    --
    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.
 
R

Robert Barrimond

First, let me say that I am not mocking AppleScript. I'm actually enjoying
the language and it's features. I won't belabor that point. Just know that
I'm having a good time with it! I was trying to avoid some well known
programming sins and I couldn't believe that Apple would force me to commit
them. It turns out my disbelief was well founded.

Second, thanks for the tips, I think they solve my problem. I'm still
getting used to filters using whose and so on.

Third, right now I'm using two "books". AppleScript in a Nutshell and the
Guide from Apple. Thanks for the suggestion on the other material. I
wonder if it's available on safari.oreilly.com?

I'm new to AppleScript and have been enjoying the learning experience,
however, I've run into a frustrating stumbling block.

First the context. I'm writing a script to do batch edits on my contacts.
For example, I'd like to use some of the custom fields and need to a
convenient way to edit these values for many, many contacts at one time.

Now the problem. I can't seem to programmatically inspect either the contact
objects or use a reference to to loop through the list of selected contacts
to edit the property the user chooses by a "choose from list" of strings.
My first strategy was to try to access the properties of the contact objects
by converting the user chosen string into a property label for each selected
contact. I couldn't find a way to do this. My second strategy was to use a
reference to create a reference that I would loop through the contacts and
then access the properties of each contact indirectly. I also failed to make
that happen. Either of these I had hoped would allow me to write a
subroutine to handle the actual looping and editing. This would be trivial
in Java, I hope AppleScript isn't forcing me to commit the Greatest
Programming Sin of All: repeat code that should be a
method/function/subroutine.

First of all, you'd better forget what you learned in other programming
languages. If you want to use AppleScript, you have to learn AppleScript. The
best way to do that - especially for people with some programming experience -
is to to buy "AppleScript: The Definitive Guide" by Matt Neuburg, published by
O'Reilly.

AppleScript actually has more powerful commands for setting a particular
property of many objects of the same class, and even for getting a filtered
set of these by 'whose' clause. But setting the property to all those objects
at once only works if the value for every object will be the same. Otherwise,
if the value will be dependent on some other property of the contact, for
example, you do need to use a loop. There are ways to speed up to traversal
of a list in a repeat loop, although the main slowdown here will be the
AppleEvents and not the list traversal. The speed-up process has to be done
differently in a subroutine than at the top level of a script, by the way, 'a
reference to', or - even faster 'my
  • ' - only works for top-level
    variables, i.e. globals (whether explicit or implicit) and properties. Read
    Matt's book for full details on implicit global-ness of top-level variables.
    The alternate method in a subroutine is not well-known, but among those who do
    it's called the "Serge script object" method. I can explain it to you.

    However, I think you have a few other questions that precede that. I can't
    actually quite make out what you're asking. Could you try in plainer English,
    using examples, and skipping the asides mocking AppleScript and other
    mutterings? I'll give it a go anyway, but I'll really not clear on what you're
    trying to do.

    HOW do want to "inspect" the contact objects and/or how do you propose to
    select them?

    Nothing could be easier than getting every contact:

    set theContacts to every contact

    ['contact' is an element of the application, which is not therefore specified.
    It is implicit. No larger container is needed in the command.]

    Or, you could filter just the particular contacts in which you may have an
    interest:

    set companyContacts to every contact whose company ‚ ""

    'whose' filtering can combine properties by logical and and orb:

    set myContacts to every contact whose company ‚ "" and name = ""
    -- name is mapped from the 'by name' of the element and is same as
    'display name' property


    set myContacts to every contact whose company ‚ "" and (custom field one =
    "" or custom field two = "" or custom field three = "")


    and so on. With this sort of selection you might even be able to do your whole
    script in one fell swoop:


    set custom field one of every contact where its category contains
    {category "Baseball"} and its custom field one = "" to "Red Sox"

    **but you can't do

    set custom field one of every contact where its category contains
    {category "Baseball"} and its custom field one = "" to (its company & ": Red
    Sox")

    For that, you'd need a repeat loop. See below.


    Alternately, you could get the selection directly from whatever the user
    selected (highlighted) in the Address Book, which is a list. You should
    include error checks for no selection, and for the class of selection as list
    (rather than text or folder) and for the class of each item as you loop later
    (in custom views you may have mixed classes of objects), but, omitting that:

    set myContacts to selection

    Here you have to use a loop since the AppleScript language does not (still)
    enable whose clauses on lists.


    I can't quite imagine the circumstances where you'd want the user to choose
    the properties from a string 'choose form list' - mostly because the
    AppleScript terms for many of of the properties (e.g. 'description') does not
    correspond to what the user is familiar with in the UI ('Notes'). Instead, I
    would recommend simply constructing your own list of user-friendly terms and
    dealing with the properties in an if/else if structure:

    set whichItems to choose from list {"First Name", Last Name", "Notes",
    "Custom 1", "Custom 2"...... etc.}
    -- you can get the actual names of the custom fields from 'address book
    1's custom field one name', etc.)

    Then when you do your loop, deal with that on each iteration:

    repeat with i from 1 to (count theContacts)
    set theContact to item i of theContacts -- see below
    if whichItems contains "Custom 1" then
    set custom field one of theContact to (theContact's company & ":
    Red Sox")
    else if whichItems contains "Custom 2" then
    set custom field two of theContact to (theContact's company & ":
    Red Sox")
    --else if ...
    --etc.
    end if
    end repeat



    Now, I don't actually know what you mean by


    My second strategy was to use a reference to create a reference that I
    would loop through the contacts and then access the properties of each contact
    indirectly. I also failed to make that happen. Either of these I had hoped
    would allow me to write a subroutine to handle the actual looping and editing.


    There are ways to use 'a reference to' in AppleScript to avoid resolving a
    variable to its value. (In the AppleScript language itself, this only happens
    "built-in" without the 'reference to' for mutable things: members of lists and
    records and for properties of dates and for script objects.) You may be able
    to do what you want.

    Chances are you're getting errors due to other mistakes. Instead of my
    guessing here, you need to provide an example.
 
P

Paul Berkowitz

First, let me say that I am not mocking AppleScript. I'm actually enjoying
the language and it's features. I won't belabor that point. Just know that
I'm having a good time with it! I was trying to avoid some well known
programming sins and I couldn't believe that Apple would force me to commit
them. It turns out my disbelief was well founded.
OK, good.

Second, thanks for the tips, I think they solve my problem. I'm still getting
used to filters using whose and so on.
I'm still intrigued with the 'by reference' reference, and would like to
know what you're trying to do. What I described last time ('by reference'
built in only for lists, records, dates and script objects, not for string,
Unicode text, integer, real, etc.) was concerned with AppleScript data
types, not with application objects. Applications have various ways of
handling references. Basically, with an application's tell block, setting a
variable to something that is an application object (like 'contact id 123'
which is how 'contact "Joe Blow"' will resolve) does already act as a
reference, built in. You can keep on referring to it, and also create a
subsidiary tell block to it rather than using 'of' if you wish. (example
below). But if you you set a variable to an object's property which itself
resolves to text or number, then it's no longer a reference - it's resolved,

Ex. 1

set address book 1's custom field one name to "Former Company" --
application-wide, all contacts
repeat with theContact in (every contact)
tell theContact
set {company, custom field one} to {"Beelzebub. Inc.", its
company}
end tell
end repeat

I specifically used the 'tell' form here, because it's the only way to set
more than one property at once, as a list. (You can can get several
properties at once by the alternate method, if you wish:

Ex. 2

set {theCompany, cust1} to theContact's {company, custom field one}


Ex. 3

In Ex. 2, both theCompany and cust1 will now be "hard-coded" text, e.g.
{"Satanic Mills, IPC", "Pookie"} and you can't set them to anything else. If
there's some reason why you might need to, then:

if whichFieldName = "Company" then -- from a choose from list
set whichFieldRef to a reference to theContact's company
else if whichFieldName = "Custom 1" then
set whichFieldRef to a reference to theContact's custom field one
else if ...
--etc
end if
set contents of whichFieldRef to "Beelzebub. Inc."

works. I don't often do this, but I had occasion to recently, and it worked.
I just tested this.

NOTE: Some applications resolve some properties to references, not to their
text or number values. (Even Entourage resolves certain properties to
objects: e.g. group member 3 of group "Whatever" will be a contact or
another group, and is a reference, but Entourage never does this with
properties defined to be text.) For example, Word 2004's object model is a
lot more complex than Entourage's. You might expect that

text object of paragraph 3 of active document

might give you the text of that text range, but it doesn't, since 'text
object' property is a text range object which has many properties. YYou get
a reference. You'd need

content of text object of paragraph 3 of active document

to get the actual text (which will be immutable). The reference is actually
very handy, since you can change all sorts of properties (including its
content) and the properties will change everywhere. (So don't be
disappointed when you see that some property, like a 'text object' of a
document is read-only. That may be so , but most of its own properties,
including its 'content' is writable.)

But I digress...
Third, right now I'm using two "books". AppleScript in a Nutshell and the
Guide from Apple. Thanks for the suggestion on the other material. I wonder
if it's available on safari.oreilly.com?

The ASLG is fine, though outdated. 95% of it still applies, though there's
another 20% or so it doesn't know about, and about another 20,000%
(application scripting) it doesn't aim to cover. There isn't much anywhere
on application scripting, since apps are all so different.

AppleScript in a Nutshell is crappy and insufficient. It's OK for explaining
some basic parts of the ASLG to programmers of other languages, sort of. The
rest is useless or wrong, including all those unscriptable Apple apps from
OS 10.0 it attempts to cover (Apple's fault, not the book's).

Matt's book is infinitely better. You can find a chapter up at the O'Reilly
Mac Dev Center, I think, and other details.

--
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.
 
Top