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.