py-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. b'docu' = document), or b'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 [b'indx'], name [b'name'], etc.), or b'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 aem.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 aem.ae bridge would be:

rootref = aem.ae.newdesc(b'null', b'')

docref = aem.ae.newrecord().coerce(b'obj ')
docref.setparam(b'want', aem.ae.newdesc(b'type', b'docu'))
docref.setparam(b'from', rootref)
docref.setparam(b'form', aem.ae.newdesc(b'enum', b'indx'))
docref.setparam(b'seld', aem.ae.newdesc(b'long', b'\x00\x00\x00\x01'))

textref = aem.ae.newrecord().coerce(b'obj ')
textref.setparam(b'want', aem.ae.newdesc(b'type', b'prop'))
textref.setparam(b'from', docref)
textref.setparam(b'form', aem.ae.newdesc(b'enum', b'prop'))
textref.setparam(b'seld', aem.ae.newdesc(b'type', b'ctxt'))

print(textref)
# <aem.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(b'docu').byindex(1).property(b'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(b'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(b'docu').byfilter(...).first

is equivalent to writing:

app.elements(b'docu').byfilter(...).elements(b'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 properties/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__(self) -- AEM references can be used as dictionary keys

    __eq__(self, value) -- AEM references can be compared for equality

    __ne__(self, value)

Properties and methods for all position specifiers

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

    Properties:
        beginning -> InsertionSpecifier
        end -> InsertionSpecifier
        before -> InsertionSpecifier
        after -> InsertionSpecifier

    Methods:
        property(self, propertycode)
            propertycode : bytes -- four-char code
            Result : Property

        userproperty(self, name)
            name : str
            Result : UserProperty

        elements(self, classcode)
            classcode : bytes -- four-char code
            Result : AllElements

        previous(self, classcode)
            classcode : bytes -- four-char code
            Result : Element

        next(self, classcode)
            classcode : bytes -- four-char code
            Result : Element
        
        -- Note: following methods are for use on
           its-based references only

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

Properties and methods for all multi-element specifiers

MultipleElements(PositionSpecifier) -- base class for all multi-
        element reference forms

    Properties:
        first -> Element
        middle -> Element
        last -> Element
        any -> Element

    Methods:
        byindex(self, key)
            key : int -- normally an integer, though some apps may 
                    accept other types (e.g. Finder accepts an Alias)
            Result : ElementByIndex

        byname(self, key)
            key : str -- the object's name
            Result : ElementByName

        byid(self, key)
            key : anything -- the object's unique id
            Result : ElementByID

        byrange(self, startref, endref)
            startref : Element -- an app- or con-based reference
            endref : Element -- an app- or con-based reference
            Result : ElementByRange

        byfilter(self, testref)
            testref : Test -- an its-based reference
            Result : ElementsByFilter

Properties and methods for all test clause classes

Test(Query) -- represents a comparison/logic test

    Properties:    
        NOT -> Test -- apply a logical 'not' test to self

    Methods:
        AND(self, *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(self, *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

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(b'ctxt')


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

Single element reference forms

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

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


ElementByID(SingleElement) -- refers to a single element in the referenced container 
        object(s) by unique id; e.g. ref.byid(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(b'cpar')

Multiple element reference forms

ElementsByRange(MultipleElements) -- refers to a range of elements
        in the referenced container object(s) (including start and 
        end points); e.g. ref.byrange(con.elements(b'cpar').byindex(2),
                con.elements(b'cpar').last)


ElementsByFilter(MultipleElements) -- refers to all elements in the
        referenced container object(s) that fulfill a given condition; 
        e.g. ref.byfilter(its.name.beginswith('a'))


AllElements(MultipleElements) -- refers to all elements of 
        the given class in the referenced container object(s); 
        e.g. ref.elements(b'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(ReferenceRoot) -- the exported 'app' variable contains
        an instance of this class

CurrentContainer(ReferenceRoot) -- the exported 'con' variable contains
        an instance of this class

ObjectBeingExamined(ReferenceRoot) -- the exported 'its' variable contains
        an instance of this class

Clients shouldn't instantiate the above classes directly; instead, they should refer to the app, con and its variables exported by AEM.

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:

        __init__(self, rootobj)
            rootobj : anything -- value to use as innermost container
                    in nested object specifiers

AEM exports the CustomRoot class via the customroot variable.