Friday, December 15, 2017
  • Index
  •  » unScripted
  •  » An Introduction to Extracting Elements & Properties from Objects

#1 2007-04-30 03:30:43 am

kai
Member
From:: Brighton, UK
Registered: 2005-05-28
Posts: 912

An Introduction to Extracting Elements & Properties from Objects

The various terms and techniques in AppleScript might initially seem a little daunting, but they usually become clearer as we focus on the various aspects of the language.

Since many of the operations we perform in AppleScript hinge on the ability to extract certain elements and properties from an AppleScript/application object, this piece (as the title suggests) introduces a few ways to achieve that. (Some of what appears below is the result of discussion in the forums here. It's by no means comprehensive, but may help to clear up one or two general questions.)

What's the difference?

First, let's just make sure we understand the distinction between elements and properties.

An element is a value/object contained within another value/object. For example, composite AppleScript values (such as lists) have elements. Simple values (such as integers or Boolean values) don't. Elements are typically referenced by index or by name.

A property of a record, value class or object is a characteristic that can be identified by a unique label. Properties are referenced using that label.

Extracting Elements

As mentioned, we can extract a specific element using the index reference form, which specifies an object or a location by describing its position in relation to the beginning or end of a container:

Applescript:


item 4 of {"elephant", "horse", "dog", "cat", "mouse"}
--> "cat"

Applescript:


second item of {"palace", "castle", "mansion", "house", "barn"}
--> "castle"

Applescript:


item -2 of {"glossy", "smooth", "irregular", "rough"}
--> "irregular"

Applescript:


first character of "story"
--> "s"

Applescript:


beginning of {"ex", "former", "previous"} & end of {"enough", "adequate", "ample"}
--> "example"

The (rarely used) middle element reference form can also be used to specify the middle object of a container's particular class:

Applescript:


middle item of {"enormous", "large", "regular", "small", "minute"}
--> "regular"

You could even use the arbitrary element reference form to randomly specify a container's object:

Applescript:


some word of "who knows what the result will be?"
--> [any one of the above words]

Application objects may usually be referred to by index or name. (Actual results might differ from those in the examples, depending on machine/configuration.)

Applescript:


tell application "Finder"
   first item of (path to applications folder)
end tell
--> application file "Address Book.app" of folder "Applications" of startup disk of application "Finder"

Applescript:


tell application "Finder"
   item "Flow 3.jpg" of (path to desktop pictures folder)
end tell
--> document file "Flow 3.jpg" of folder "Desktop Pictures" of folder "Library" of startup disk of application "Finder"

The filter reference form can be used to specify a container's application objects that match the condition(s) of a Boolean expression.

Applescript:


tell application "Finder"
   name of files of (path to fonts) whose name begins with "Times"
end tell
--> {"Times LT MM", "Times.dfont", "TimesLTMM"}

Or, to get just one item:

Applescript:


tell application "Finder"
   name of first file of (path to preferences folder) whose name contains "applescript"
end tell
--> "com.apple.applescript.plist"

Note that the filter reference form can't be used to extract values from AppleScript objects (such as lists) to match specified criteria. However, see the 'sidebar' below...

Sidebar: Extracting values from AppleScript lists by type class

A form of filtering is possible with AppleScript lists that contain different type classes, so that items of a specified class can be extracted:

Applescript:


set mixed_list to {path to desktop, "some plain text", 93, current date, "some Unicode text" as Unicode text, 5.4, true, {1, 2, 3}, {a:"a", b:"b"}, "more plain text", path to me, window 1, words of "this will be a list", 57, "more Unicode text" as Unicode text, {name:name, version:version}, pi, date ("1/" & "1"), 2 + 2 = 5, item 1 of application "Finder"}

every alias of mixed_list --> {alias "Macintosh HD:Users:kai:Desktop:", alias "Macintosh HD:Applications:AppleScript:Script Editor.app:"}

every Unicode text of mixed_list --> {"some Unicode text", "more Unicode text"}

mixed_list's strings --> {"some plain text", "more plain text"}

mixed_list's numbers --> {93, 5.4, 57, 3.14159265359}

mixed_list's integers --> {93, 57}

mixed_list's reals --> {5.4, 3.14159265359}

mixed_list's lists --> {{1, 2, 3}, {"this", "will", "be", "a", "list"}}

mixed_list's records --> {{a:"a", b:"b"}, {name:"Script Editor", version:"1.10.6"}}

mixed_list's booleans --> {true, false}

mixed_list's dates --> {date "Sunday, September 24, 2006 19:12:17", date "Sunday, January 1, 2006 00:00:00"}

mixed_list's references --> {window id 2884, alias file "blah.txt" of desktop of application "Finder"}


The relative reference form specifies an object or a location by describing its position in relation to another object (known as the base) in the same container. This next example gets the names of Apple menu items that are adjacent to certain menu separators:

Applescript:


tell application "System Events" to tell menu 1 of menu bar item "Apple" of menu bar 1 of application process "Finder"
   
   set separator_1 to first menu item whose title is ""
   set last_name_in_group_1 to name of menu item before separator_1
   set first_name_in_group_2 to name of menu item after separator_1
   
   -- and just to demonstrate a different syntax form:
   set separator_2 to second menu item where title is ""
   set last_name_in_group_2 to name of menu item in front of separator_2
   set first_name_in_group_3 to name of menu item in back of separator_2
   
end tell

{last_name_in_group_1:last_name_in_group_1, first_name_in_group_2:first_name_in_group_2, last_name_in_group_2:last_name_in_group_2, first_name_in_group_3:first_name_in_group_3}

--> {last_name_in_group_1:"Mac OS X Software…", first_name_in_group_2:"System Preferences…", last_name_in_group_2:"Location", first_name_in_group_3:"Recent Items"}

Extracting Properties

Now let's take a look at the ways in which we could extract properties from a record or object.

Probably the most common method is to extract each property individually:

Applescript:


set item_info to info for (path to documents folder) without size
set item_name to item_info's name
set item_kind to item_info's kind
display alert "The name of this " & item_kind & " is \"" & item_name & "\"."

Another way is to specify the required properties using a list:

Applescript:


set {item_name, item_kind} to {name, kind} of (info for (path to applications folder) without size)
display alert "The name of this " & item_kind & " is \"" & item_name & "\"."

Since object properties take the form of a record, yet another approach is to actually use a record to extract the values:

Applescript:


set {name:item_name, kind:item_kind} to info for (path to music folder) without size
display alert "The name of this " & item_kind & " is \"" & item_name & "\"."

We can sometimes extract further properties from the initial values of a property.

For example, let's say we wanted to determine both the name of a file and the length of its name extension. Normally, we might achieve this in a couple of hits — the first to get the text value, the second to get its length. (We'll use Finder here in preference to the info for command since, if a file has no name extension, the latter may return a class of 'missing value' — rather than the more sensible empty string.)

Applescript:


tell application "Finder"
   set {file_name, name_ext} to {name, name extension} of (choose file)
   set ext_len to length of name_ext
   display alert "File name: " & file_name message "Length of name extension: " & ext_len
end tell

Bear in mind here that length is a property of the (Unicode) text returned for the file's name extension property, so you won't find a reference to it in Finder's AppleScript dictionary.

Nevertheless, by using nested records, we can extract both values in a single statement:

Applescript:


tell application "Finder"
   set {name:file_name, name extension:{length:ext_len}} to choose file
   display alert "File name: " & file_name message "Length of name extension: " & ext_len
end tell

When I first used this extraction method in these forums, it prompted the question: "What else can go in the second level braces?"

The answer is pretty much any property that exists at a subsequent level. The technique isn't necessarily restricted to just a couple of levels, either:

Applescript:


tell application "Finder"
   activate
   open (path to pictures folder) (* just to give us a demo window *)
   set {class:c1, name:n1, target:{class:c2, name:n2, container:{class:c3, name:n3, container:{class:c4, name:n4, container:{class:c5, name:n5}}}}} to Finder window 1
   display alert "Front Window:" message (c1 as string) & " \"" & n1 & "\" of " & c2 & " \"" & n2 & "\"" & " of " & c3 & " \"" & n3 & "\"" & " of " & c4 & " \"" & n4 & "\"" & " of " & c5 & " \"" & n5 & "\"."
end tell

Finder has some more useful examples of nested properties, too:

Applescript:


tell application "Finder" to tell Finder window 1
   if not (exists) then return display alert "No Finder windows open" message "Please open any folder and try again."
   set {name:n, sidebar width:b, statusbar visible:v, list view options:{icon size:i, calculates folder sizes:c, uses relative dates:r, sort column:{name:s, width:w, sort direction:d}}} to it
   display alert "Example of nested Finder window properties:" message "FINDER WINDOW:
   name: "
& n & "
   sidebar width: "
& b & "
   statusbar visible: "
& v & "
   
   LIST VIEW OPTIONS:
       icon size: "
& i & "
       calculates folder sizes: "
& c & "
       uses relative dates: "
& r & "
   
       SORT COLUMN:
           name: "
& s & "
           width: "
& w & "
           sort direction: "
& d
end tell

Avoiding Evaluation Errors

Another thing to remember with this technique is that, when attempting to extract certain properties in some situations, the result may not be automatically evaluated — which could lead to an error:

this will result in a coercion failure:

Applescript:


set {name extension:{class:class_returned}} to info for (choose file without invisibles)
display alert "Class of name extension: " & class_returned

However, an evaluation can be invoked by either first setting a variable to the file information record, using an explicit get statement — or making the info for call the target of a tell statement...

setting a variable:

Applescript:


set some_variable to (info for (choose file without invisibles))
set {name extension:{class:class_returned}} to some_variable
display alert "Class of name extension: " & class_returned

using an explicit get statement:

Applescript:


set {name extension:{class:class_returned}} to get info for (choose file without invisibles)
display alert "Class of name extension: " & class_returned

using a tell statement:

Applescript:


tell (info for (choose file without invisibles)) to set {name extension:{class:class_returned}} to it
display alert "Class of name extension: " & class_returned

More About AppleScript Properties

In AppleScript, value classes fall basically into two categories: simple values (such as integers and real numbers) — which contain no other values, and composite values (such as lists and records) — which do. Some value classes (such as lists, records, strings [plain, styled or international text] and Unicode text) have a length property. Some may have other properties, such as contents (for a reference).

Date values, of course, have a range of properties — such as day, weekday, month, year, time, date string and time string — which can also be extracted using nested records:

Applescript:


set {name:n, kind:k, creation date:{date string:d, time string:t}} to info for (choose file)
display alert k & ": " & n message "The " & k & " was created on " & d & " at " & t & "."

And here's how a couple of AppleScript's list properties, rest and reverse, might be used to extract the width and height dimensions of the desktop:

Applescript:


tell application "Finder"
   set {bounds:{rest:{rest:{w, h}}}} to desktop's window
   display alert "Desktop dimensions:" message "Width:" & tab & w & return & "Height:" & tab & h
end tell

Applescript:


tell application "Finder"
   set {bounds:{reverse:{h, w}}} to desktop's window
   display alert "Desktop dimensions:" message "Width:" & tab & w & return & "Height:" & tab & h
end tell

More About Application Elements & Properties

How do we effectively identify an application object's elements and properties in the first place?

Well, at the risk of repeating oft-quoted advice yet again, the first port of call whenever targeting a scriptable application should be to check out its Applescript dictionary. While some dictionaries might be more informative/accurate than others, they're still our best starting point — and should actually list the relevant elements and properties of each object class for us. An AS dictionary may not be the easiest read, either. But the more we try it, the easier it will become to decipher.

There are some other little tricks that might help, too — such as using a supplementary scriplet to check for elements or properties.

The following statement, for example, which gives details of menu items contained by the Apple menu, uses nothing more than a general class in System Events, UI element:

Applescript:


tell application "System Events" to UI elements of UI element 1 of UI element 1 of UI element 1 of process "Finder"

--> {menu item "About This Mac" of menu "Apple" of menu bar item "Apple" of menu bar 1 of application process "Finder" of application "System Events", menu item "Software Update…" of menu "Apple" of menu bar item "Apple" of menu bar 1 of application process "Finder" of application "System Events", etc...}

This, in turn, shows the properties of the first menu item (OK, in spite of the number of items returning 'missing value', we should still be able to glean some information that might prove useful.)

Applescript:


tell application "System Events" to properties of UI element 1 of UI element 1 of UI element 1 of UI element 1 of process "Finder"

--> {position:{0, 4}, maximum value:missing value, name:"About This Mac", size:{223, 19}, subrole:missing value, class:menu item, minimum value:missing value, enabled:true, selected:false, role:"AXMenuItem", help:missing value, title:"About This Mac", value:missing value, entire contents:{}, description:"menu item", focused:missing value, orientation:missing value}

And this variation even indicates what actions the target should respond to:

Applescript:


tell application "System Events" to name of actions of UI element 1 of UI element 1 of UI element 1 of UI element 1 of process "Finder"

--> {"AXCancel", "AXPress"}

Spoilt for Choice?

We're each at various points along the learning curve of stuff like this, and it's important not to be thrown by all the options. If you have an extraction method that works for you, don't concern yourself too much with trying to absorb every alternative technique. (It's just interesting to try out a different approach now and then.)

The main point is that, by gradually gaining a better understanding of the structure of AppleScript and application objects, along with a greater appreciation of their properties, we should find that our scripting efforts become both easier and more effective.

I hope these notes help in some small way to achieve that...

smile


kai

Offline

 
  • Index
  •  » unScripted
  •  » An Introduction to Extracting Elements & Properties from Objects

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)