rb-appscript

4. References

About references

An Apple Event Object Model query (a.k.a. "reference") essentially consists of a linked list made up of one or more Apple event descriptors (AEDescs) of, for the most part, typeObjectSpecifier. Object specifiers are used to identify properties and elements in the application's AEOM. Each object specifer contains four fields:

want
four-char-code indicating desired element(s)'s class code (e.g. 'docu' = document), or 'prop' if it's a property specifier
from
an object specifer identifying container object(s)
form
four-char-code indicating how the element(s) should be selected (by index ['indx'], name ['name'], etc.), or 'prop' if it's a property specifier
seld
selector data (e.g. in a by-name specifier, this would be a string)

The Apple Event Manager (and, in turn, the AE extension) provides several ways to construct object specifiers and assemble them into a complete reference, but these are all rather verbose and low-level. AEM hides all these details behind an object-oriented wrapper that uses chained property and method calls to gather the data needed to create object specifiers and assemble them into linked lists.

For example, consider the reference text of document 1. The code for constructing this reference using the low-level AE bridge would be:

rootref = AE::AEDesc.new("null", "")

docref = AE::AEDesc.new_list(true).coerce("obj ")
docref.put_param("want", AE::AEDesc.new("type", "docu"))
docref.put_param("from", rootref)
docref.put_param("form", AE::AEDesc.new("enum", "indx"))
docref.put_param("seld", AE::AEDesc.new("long", "\x00\x00\x00\x01"))

textref = AE::AEDesc.new_list(true).coerce("obj ")
textref.put_param("want", AE::AEDesc.new("type", "prop"))
textref.put_param("from", docref)
textref.put_param("form", AE::AEDesc.new("enum", "prop"))
textref.put_param("seld", AE::AEDesc.new("type", "ctxt"))

p textref
#<AE::AEDesc type="obj " size=152>

This code works by creating an AEDesc of typeObjectSpecifier to represent each specifier and populating its fields one at a time. Each AEDesc is nested within the next to form a linked list of object specifier records; the last (innermost) descriptor in the finished list indicates the reference's root object in the AEOM (in this case, the application object, which is represented by a null descriptor).

Now, compare the above with the AEM equivalent:

app.elements('docu').by_index(1).property('ctxt')

As you can see, AEM still uses low-level four-character codes to identify the text property and document class, but is otherwise a high-level object-oriented API. Once again, each reference begins with a root object, in this case AEM.app. New AEM specifiers are constructed by method calls; each call returning a new specifier object whose own methods can be called, and so on. This allows clients to build up a chain of AEM specifier objects that aem can later pack into AEDescs for sending to applications.

One more thing to notice: in AEM, specifying a class of elements and indicating which of those elements should be selected are performed by separate method calls, although the information provided will eventually be packed into a single AEDesc of typeObjectSpecifier. This two-step approach makes it easier to integrate aem with the higher-level appscript bridge, which also uses two calls to construct element specifiers (one to specify the element class, e.g. documents, and another to specify the selection, e.g. [1]).

Note that app.elements('docu') is itself a valid reference, identifying all the document elements of the application class. You do not have to call an explicit #all selector (indeed, none is provided) as AEM automatically handles the details for you. AEM even allows for some convenient shorthand, e.g. writing:

app.elements('docu')by_filter(...).first

is equivalent to writing:

app.elements('docu')by_filter(...).elements('docu').first

This allows clients to specify the first document that matches the given condition without having to specify the element class a second time. In AppleScript, the equivalent to this is:

first document whose ...

which is short for:

first document of (documents whose ...)

(Again, this additional behaviour primarily exists to serve the syntactically sugared appscript layer.)

Reference forms

AEM defines a number of classes representing each of the AEOM reference forms. There are eight AEOM reference forms:

(Actually, there's nine forms if you count the 'user property' reference form, although this is only used by OSA (e.g. AppleScript Editor) applets to identify script properties. AEM supports this extra form more for sake of completeness than usefulness.)

Each of these reference forms is represented by a different AEM specifier class, apart from the absolute position form which is represented by three different classes according to the kind of selector used: a numerical index (e.g. 1, -3), a named ordinal identifying a single element (first, middle, last, any), or a named ordinal identifying all elements (all).

The following diagram shows the AEM reference class hierarchy (slightly simplified for legibility); concrete classes are shown in bold:

AEM reference class hierarchy

Note that the user shouldn't instantiate these classes directly; instead, AEM will instantiate them as appropriate when the client calls the methods of other AEM reference objects, starting with the app, con and its objects that form the root of all AEM references.

In fact, it really isn't necessary to remember the reference class hierarchy at all, only to know which concrete classes implement which methods. All user-accessible properties and methods are defined by just four superclasses:

Query
Defines comparison and hashing methods.
PositionSpecifier
Defines methods for identifying properties and all elements, insertion locations, elements by relative position. Also defines comparison and logical test methods for use in constructing its-based references.
MultipleElements
Defines methods for identifying specific elements of a multi-element reference.
Test
Defines logical test methods for use in constructing its-based references.

Base classes

Basic methods

Query -- Base class for all reference form and test clause classes.
    hash -- aem references can be used as dictionary keys

    ==(value) -- aem references can be compared for equality

Methods for all position specifiers

PositionSpecifier < Specifier -- base class for all property and element
        reference forms (i.e. all forms except insertion location)

    Methods:

        beginning
            Result : InsertionSpecifier

        end
            Result : InsertionSpecifier

        before
            Result : InsertionSpecifier

        after
            Result : InsertionSpecifier

        property(code)
            code : str -- four-char property code, e.g. 'pnam'
            Result : Property

        user_property(name)
            name : str
            Result : UserProperty

        elements(ccode)
            code : str -- four-char class code, e.g. 'docu'
            Result : AllElements

        previous(code)
            code : str -- four-char class code
            Result : Element

        next(code)
            code : str -- four-char class code
            Result : Element
        
        -- Note: following methods are for use on
           its-based references only

        gt(val) -- self is greater than value
            val : anything
            Result : Test
        
        ge(val) -- self is greater than or equal to value
            val : anything
            Result : Test
            
        eq(val) -- self equals value
            val : anything
            Result : Test
    
        ne(val) -- self does not equal value
            val : anything
            Result : Test
    
        lt(val) -- self is less than value
            val : anything
            Result : Test
    
        le(val) -- self is less than or equal to value
            val : anything
            Result : Test
    
        begins_with(val) -- self begins with value
            val : anything
            Result : Test
    
        ends_with(val) -- self ends with value
            val : anything
            Result : Test
    
        contains(val) -- self contains value
            val : anything
            Result : Test
    
        is_in(val) -- self is in value
            val : anything
            Result : Test

Methods for all multi-element specifiers

MultipleElements < PositionSpecifier -- base class for all multi-
        element reference forms

    Methods:

        first
            Result : Element

        middle
            Result : Element

        last
            Result : Element

        any
            Result : Element

        by_index(key)
            key : integer -- normally an integer, though some apps may 
                    accept other types (e.g. Finder accepts a MacTypes::Alias)
            Result : ElementByIndex

        by_name(key)
            key : string -- the object's name
            Result : ElementByName

        by_id(key)
            key : anything -- the object's unique id
            Result : ElementByID

        by_range(start, stop)
            start : Element -- an app- or con-based reference
            stop : Element -- an app- or con-based reference
            Result : ElementByRange

        by_filter(test)
            test : Test -- an its-based reference
            Result : ElementsByFilter

Methods for all test clause classes

Test < Query -- represents a comparison/logic test

    Methods:

        and(*operands) -- apply a logical 'and' test to self and
                one or more other operands
            *operands : Test -- one or more comparison/logic test
                objects
            Result : Test
            
        or(*operands) -- apply a logical 'or' test to self and one
                    or more other operands
            *operands : Test -- one or more comparison/logic test
                objects
            Result : Test

        not -- apply a logical 'not' test to self
            Result : Test

Concrete classes

Insertion location reference form

InsertionSpecifier < Specifier -- refers to insertion point before or after/at
        beginning or end of element(s); e.g. ref.before

Property reference forms

Property < PositionSpecifier -- refers to a property (whose value
        may be a basic type, application object or reference);
        e.g. ref.property('ctxt')


UserProperty < PositionSpecifier -- refers to a user-defined property 
        (typically in an OSA applet); e.g. ref.user_property('myVar')

Single element reference forms

ElementByIndex < SingleElement -- refers to a single element in the referenced 
        container object(s) by index; e.g. ref.by_index(3)

ElementByName < SingleElement -- refers to a single element in the referenced 
        container object(s) by name; e.g. ref.by_name('Documents')


ElementByID < SingleElement -- refers to a single element in the referenced container 
        object(s) by unique id; e.g. ref.by_id(3456)


ElementByOrdinal < SingleElement -- refers to first, middle, last or any element in 
        the referenced container object(s); e.g. ref.first


ElementByRelativePosition < SingleElement -- refers to the previous or next element 
        of the given class in the referenced container object(s); 
        e.g. ref.next('cpar')

Multiple element reference forms

ElementsByRange < MultipleElements -- refers to a range of elements
        in the referenced container object(s) (including beginning and 
        end points); e.g. ref.by_range(AEM.con.elements('cpar').by_index(2),
                AEM.con.elements('cpar').last)


ElementsByFilter < MultipleElements -- refers to all elements in the
        referenced container object(s) that fulfill a given condition; 
        e.g. ref.by_filter(AEM.its.property('pnam').begins_with('a'))


AllElements < MultipleElements -- refers to all elements of the given class
        in the referenced container object(s); e.g. ref.elements('docu')

Tests

The Test class represents a comparison test or logical test, and defines methods for composing additional logical tests on top of these. Each kind of test clause is represented by a different subclass of the main Test class. The details are not that important, however, so they're not listed here.

Reference Roots

The following classes are used to construct standard AEM references:

ApplicationRoot < PositionSpecifier
       -- AEM.app returns an instance of this class

CurrentContainer < PositionSpecifier
       -- AEM.con returns an instance of this class

ObjectBeingExamined < PositionSpecifier
       -- AEM.its returns an instance of this class

Clients shouldn't instantiate the above classes directly; instead, they should use the AEM.app, AEM.con and AEM.its methods which return the appropriate objects.

The CustomRoot class is used to construct AEM references with a non-standard root:

CustomRoot(ReferenceRoot) -- used to construct references with
        a custom root

    Constructor:

        initialize(value)
            value : anything -- value to use as innermost container
                    in nested object specifiers

Clients can use the AEM.custom_root method to create new CustomRoot instances, passing the root object as method's sole argument.