py-aem manual | 4. References

#

## 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:

- insertion location
- property
- element by absolute position (index or ordinal)
- element by name
- element by id
- element by relative position
- elements by range
- elements by test

(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:

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.