Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Relevant replacement documents include:
MoreOSL/ReadMe.html
<HTML> |
<HEAD> |
<TITLE>Read Me</TITLE> |
</HEAD> |
<BODY BGCOLOR="#FFFFFF"> |
<H1>Read Me About MoreOSL</H1> |
<P>1.0b1</P> |
<P>MoreOSL, or MOSL for short, is a source code library for |
implementing AppleScript support within your application. It has the |
following key features.</P> |
<UL> |
<LI>C implementation -- Many AppleScript support frameworks (for |
example, PowerPlant, MacApp) are available to C++ applications |
only. MOSL is entirely written in C. |
<LI>object focused -- Historically, DTS AppleScript support |
samples have concentrated on supporting text scripting. MOSL |
ignores text scripting and concentrates on the scripting of |
discrete objects, such as windows and items within windows. |
<LI>modern -- MOSL incorporates modern AppleScript techniques, |
such as 'deep' object resolution. It is also fully Carbon |
compatible. |
<LI>well tested -- MOSL includes a test application, TestMoreOSL, |
that demonstrates its capabilities. It also includes a large suite |
of AppleScript-based tests. |
<LI>comprehensive -- MOSL allows you to easily implement the bulk |
of the core event suite. MOSL supports all key forms except |
<CODE>formRelativePosition</CODE>. MOSL also supports data |
comparison for most revelant data types. |
</UL> |
<P>The MoreOSL library is targetted to run on a PowerPC with Mac OS |
8.5 and above. It compiles and works best under Carbon, but it will |
compile and work with InterfaceLib.</P> |
<H2>Packing List</H2> |
<P>The MoreOSL folder of the sample contains the following items: |
</P> |
<UL> |
<LI>"ReadMe.html" -- This documentation file. |
<LI>"MoreOSL.h" and "MoreOSL.c" -- The core scripting support |
library. This is the code that I expect you will want to drop |
straight into your application. |
<LI>"MoreOSLStringCompare.h", "MoreOSLStringCompare.c", |
"MoreOSLStringCompare.rsrc" and "MoreOSLStringCompare.script" -- A |
string comparison engine that supports all the AppleScript string |
operators in an international friendly fashion. See |
<A HREF="#MoreOSLStringCompare">below</A> for more details on why |
this is hard. |
<LI>"MoreOSLTokens.h" and "MoreOSLTokens.c" -- The definition of, |
and operations upon, the <CODE>MOSLToken</CODE> data type. This is |
the simple token structure used by MOSL. You may want to add or |
modify this structure for your application. See |
<A HREF="#TokenFormat">Token Format</A> for more details. |
<LI>"MoreOSLHelpers.h" and "MoreOSLHelpers.c" -- Code that |
supports standard properties of standard system objects, such as |
the application itself and windows. The code assumes that you're |
using the Mac OS 8.5 Window Manager interfaces. This code was |
written to get TestMoreOSL running; you may want to use it or, |
then again, you may choose to ignore it. |
<LI>"TestMoreOSL" -- A folder containing the MOSL test |
application, TestMoreOSL. |
</UL> |
<P>The remaining folders contain components of the DTS sample code |
library MoreIsBetter that are needed for TestMoreOSL.</P> |
<H2>Using the Sample</H2> |
<H3>Getting Started</H3> |
<P>You probably want to start by launching the TestMoreOSL |
application. I've included a Carbon version in the sample package. |
You should install CarbonLib 1.0.2 or higher before running this |
application.</P> |
<P><TABLE BORDER=0 WIDTH=498> |
<TR> |
<TD bgcolor="#FFFF99"> |
<P><B>WARNING:<BR> |
</B>The debug version of TestMoreOSL logs extensively to |
BBEdit. When you run it, it will create a new window in |
BBEdit and spew lots of logging information to the window. |
You probably want to close any important documents you might |
have open in BBEdit before launch the application. |
</TD></TR> |
</TABLE></P> |
<P>To run the test application, simply launch it in the Finder. The |
application will bring up a new, untitled document window. You can't |
manipulate the window using the mouse, so don't even try. |
<STRONG>TestMoreOSL only supports an AppleScript interface, it does |
not support the user interface.</STRONG></P> |
<P>Once TestMoreOSL is running, you can start scripting it. You might |
want to drop the TestMoreOSL application on Script Editor to look at |
its dictionary. Alternatively, you can open the "Test Script" script |
(in the TestMoreOSL folder) and run it. This script displays a list |
of test choices to run. You can run one of many different tests and |
demos by selecting them and clicking Run.</P> |
<H3>Seeing the Log Output</H3> |
<P>If BBEdit is running while you execute your tests, you will see a |
massive amount of logging information spewing out to an untitled |
document in BBEdit. This is a log of how TestMoreOSL is processing |
the incoming Apple events. You might want to stop the logging, either |
because you want to use BBEdit or because it's too slow, or for some |
other reason. There are a number of ways to do this.</P> |
<OL> |
<LI>If you build the sample with debugging turned off, no log |
output will be generated (in fact, all the logging code is |
compiled out). |
<LI>If you launch TestMoreOSL and then close all the windows in |
BBEdit, the logging will be generated but won't be displayed. |
<LI>The following AppleScript command will turn off all logging in |
the debug version. <TABLE BORDER=0> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<PRE>tell application "TestMoreOSL" |
set debug to 0 |
end tell</PRE> |
</TD></TR> |
</TABLE> |
</OL> |
<P>In order to save time, "Test Script" uses this last feature to |
disable all logging if you are running all tests. To find out what |
the various bits in the <CODE>debug</CODE> property mean, look for |
their definitions in "MoreOSL.h".</P> |
<H3>Use MoreOSL in Your Code</H3> |
<P>To find out more about how to use the MoreOSL library in your own |
code, see <A HREF="#UsingMOSL">Adding Scriptability using |
MoreOSL</A>.</P> |
<H2>Building the Sample</H2> |
<P>The sample was built using the standard MoreIsBetter build |
environment (CodeWarrior Pro 2 compiler with Universal Interfaces |
3.3.1). You should be able to build the project in CodeWarrior Pro |
5.3 without difficulty. To build it, up the project, select either |
the "Dbg-Carbon" or the "Dbg-PPC" target, and choose Make from the |
Project menu. Either target will build a TestMoreOSL application. |
</P> |
<P>Both targets build to the same application name because that makes |
it easier to maintain the "Test Script".</P> |
<H2><A NAME="UsingMOSL"></A>Adding Scriptability using MoreOSL</H2> |
<P>Even with MOSL, adding scriptability to your application is still |
not easy. I suggest you take the following steps.</P> |
<H3>Design Your Dictionary</H3> |
<P>It's important that you design your AppleScript dictionary before |
you start coding. Think in terms of what your target user needs to |
do, not in terms of how your application works internally. A well |
designed dictionary actually makes the job of implementing scripting |
support easier, because you know a priori the list of classes, the |
elements within each class, and the properties of each class.</P> |
<P>I'm not an AppleScript dictionary expert, so you can't take |
TestMoreOSL as a paragon of good dictionary design. Instead, you |
should probably start by looking at other, well designed and easily |
scripted applications. Also, you might want to look at some of the |
resources on the |
<A HREF="http://developer.apple.com/techpubs/macos8/InterproCom/AppleScriptDev/applescriptdev.html">AppleScript |
for Developers</A> web site. I find Cal Simone's classic |
<A HREF="http://developer.apple.com/dev/techsupport/develop/bysubject/iac.html">develop |
Articles</A> to be especially helpful.</P> |
<H3><A NAME="TokenFormat"></A>Token Format</H3> |
<P>Once you know what classes you have to support, the next step is |
to think about tokens. When I started MOSL, understanding what would |
be a good token format was the hardest initial design obstacle. |
Fortunately, MOSL makes decision much easier for you.</P> |
<P>MOSL uses a very simple design for its tokens, as described in |
"MOSLTokens.h". The basic format is a fix-sized record. The fields |
are defined and used by MOSL, except for the <CODE>tokData</CODE> |
field, which is specificially intended to store a pointer to the |
native object that the token represents. If you're using C++ (why |
aren't you using the PowerPlant or MacApp scripting support!?!), you |
can use this to store a C++ object pointer. If you're using C, you |
typically store a pointer to your 'object' equivalent, such as a |
<CODE>DialogRef</CODE> or <CODE>WindowRef</CODE>, or a pointer to the |
data structure that holds the object's state.</P> |
<P>The meaning of <CODE>tokData</CODE> is class-specific, so you |
don't have to store the same data in the field for each class. For |
example, TestMoreOSL stores a <CODE>DialogRef</CODE> in |
<CODE>tokData</CODE> for all window-like classes |
(<CODE>cDocument</CODE>, <CODE>cWindow</CODE>, |
<CODE>cNodeWindow</CODE>, <CODE>cAboutWindow</CODE>) and stores a |
pointer to the underlying <CODE>Node</CODE> structure for nodes |
(<CODE>cNode</CODE>).</P> |
<P>Remember, your token should never be the actual data itself, it |
should represent the data (that is, be a pointer to the data). This |
is critical to understand because the token is passed to both your |
getter and setter object primitives (see below).</P> |
<P>Finally, it is possible for you to safely extend the |
<CODE>MOSLToken</CODE> structure to add extract fields. All of the |
routines that MOSL uses to operate on tokens are in the |
"MOSLTokens.c" file, so your changes should be isolated to that file. |
</P> |
<H3>First Code</H3> |
<P>The first code you should write is to call the |
<CODE>InitMoreOSL</CODE> routine. You must pass it the |
<A HREF="#EventTable">event table</A>, the |
<A HREF="#ClassTable">class table</A>, the |
<A HREF="#DefaultEventHandler">default event handler</A>, and a |
error-to-string callback. The event table and class table are later |
sections. The other parameters are discussed here.</P> |
<P>The error-to-string callback is necessary because, if an Apple |
event fails, MOSL automatically handles putting an error message into |
the reply. However, MOSL does not have access to localised resources, |
so there's no way it can put the correct localised error message into |
the reply. To get the localised error message, it calls the |
error-to-string callback. Your initial prototype need not include an |
error to string callback (TestMOSL doesn't) but a final product must. |
</P> |
<H3><A NAME="EventTable"></A>Event Table</H3> |
<P>The next step is to construct your event table. This is an array |
of <CODE>MOSLEventEntry</CODE> structures which describe the Apple |
events to which your application responds. The exact structure is |
given in "MoreOSL.h", but there are some important things you need to |
know.</P> |
<UL> |
<LI>The order of the events in the array is significant to MOSL |
only in that it must match the order in the |
<A HREF="#ClassEventHandlers">class event handler table</A> for |
each class. |
<LI>You can work out the list of events in the event table by |
simply looking through your dictionary. See, I told you that |
designing your dictionary first was a good idea. |
<LI>For each event, you must specify whether the direct object is |
required, illegal, or optional. You must also specify how the |
result should be treated. For the core event suite, you can just |
copy this information from the <CODE>kEventTable</CODE> in |
"TestMoreOSL.c". |
<LI>The event table doesn't actually contain pointers to event |
handlers. The actual event handlers are supplied as part of the |
class table. |
</UL> |
<H3><A NAME="ClassTable"></A>Class Table</H3> |
<P>The class table is an array of <CODE>MOSLClassEntry</CODE> |
structures that describes the classes that your application supports. |
Each class table entry contains the following information.</P> |
<UL> |
<LI>The ID of the class itself. You can get this from your |
dictionary. |
<LI>A pointer to a <A HREF="#PropertyTable">property table</A>. |
<LI>A pointer to a <A HREF="#ClassEventHandlers">class event |
handlers table</A>. |
<LI>A set of <A HREF="#ObjectPrimitives">object primitives</A>. |
</UL> |
<P><A NAME="PropertyTable"></A>The property table is an array of |
<CODE>MOSLPropEntry</CODE> structures that describes the properties |
for the class. Each entry indicates the property name (actually, a |
four character code which the dictionary translate to a user-visible |
name) and whether the property is read-only. Both pieces of |
information are readily available from your dictionary. Your class's |
property table should be terminated by either a property of name |
<CODE>kMOSLPropNameLast</CODE>, or a property of name |
<CODE>pInherits</CODE> if this class inherits properties from another |
class. See "MoreOSL.h" for exact details on how this is set up.</P> |
<P><A NAME="ClassEventHandlers"></A>The class event handlers table is |
an array of function pointers that MOSL calls when it receives an |
Apple event whose direct object is in your class. The class event |
handlers table is indexed by the same indices as used in the |
<A HREF="#EventTable">event table</A>. Every class must have an entry |
for every event, although if the event makes no sense for a class you |
can set the entry to nil. Finally, MOSL provides a number of general |
class event handlers for handling the "count", "exists", "get data", |
and "set data" events in terms of your class's object primitives. In |
most cases, you can use these general event handlers for these events |
and just implement the object primitives.</P> |
<P><A NAME="ObjectPrimitives"></A>The object primitives for a class |
are individual callbacks that MOSL calls under specific |
circumstances. Unlike the class event handlers, each object |
primitives has a different prototype. Like the class event handlers, |
MOSL provides a number of general object primitive routines that you |
can use to implement one primitive in terms of the others.</P> |
<UL> |
<LI>"getter" is the only primitive that is absolutely required for |
each class. MOSL uses this primitive to access properties of |
objects of the class, and to get the canonical object specifier |
for objects of the class. |
<LI>"setter" is required if the class has any properties that can |
be modified. |
<LI>"counter" and "accessByIndex" are required if objects of the |
class have elements. |
<LI>"accessByUniqueID" is required if objects of the class have |
elements and you want to support accessing those elements by |
unique ID. If your class supports the "counter", "accessByIndex", |
and a unique ID property that can be obtained by calling "getter", |
you can use the <CODE>MOSLGeneralAccessByUniqueID</CODE> in place |
of your own primitive. |
<LI>"accessByName" is required if objects of the class have |
elements and you want to support accessing those elements by name. |
If your class supports the "counter", "accessByIndex", and a name |
property that can be obtained by calling "getter", you can use the |
<CODE>MOSLGeneralAccessByName</CODE> in place of your own |
primitive. |
<LI>"coerceToken" is required for any class that is derived from a |
base class. It is not necessary for the base class (or any other |
standalone class). The primitive allows MOSL to coerce tokens from |
a derived class to the base class, which is necessary for object |
comparisons and <CODE>formRange</CODE> support. |
</UL> |
<H3>Implementing MOSL Callbacks</H3> |
<P>As you implement the various MOSL callbacks for your classes, you |
should keep the following in mind.</P> |
<UL> |
<LI>Look carefully at the comments in "MoreOSL.h" for an exact |
specification of what you must do in your class event handlers and |
object primitives. |
<LI>For easy, if possibly inefficient, code, use MOSL's general |
class event handlers (<CODE>MOSLGeneralCount</CODE>, |
<CODE>MOSLGeneralExists</CODE>, <CODE>MOSLGeneralGetData</CODE>, |
<CODE>MOSLGeneralSetData</CODE>) and object primitives |
(<CODE>MOSLGeneralAccessByUniqueID</CODE>, |
<CODE>MOSLGeneralAccessByName</CODE>) where possible. |
<LI>When accessing Apple event parameters in your class event |
handlers, get the parameter using <CODE>AEGetParamDesc</CODE> with |
<CODE>typeWildCard</CODE> and then call |
<CODE>MOSLCoerceObjDesc</CODE> or |
<CODE>MOSLCoerceObjDescToPtr</CODE> to coerce the data to the |
appropriate type. This ensures that commands whose arguments are |
properties or objects (like the one shown below) work |
correctly.<TABLE BORDER=0> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<PRE>tell application "TestMoreOSL" |
set name of window 1 to name of window 2 |
end tell</PRE> |
</TD></TR> |
</TABLE> |
<LI>Your "getter" object primitive must implement two distinct |
pieces of functionality. |
<OL> |
<LI>When passed a property token (<CODE>tokType</CODE> is |
<CODE>typeProperty</CODE>), your getter must return the value |
of the property itself. If the property is a reference to |
another object, the value must be the token for that object. |
<LI>When passed an object token (<CODE>tokType</CODE> matches |
the class for the "getter" object primitive), your getter must |
return an object specifier for that token. To create the |
container object specifier, your getter should construct a |
token for the container and call the container class's getter. |
See <CODE>CNodeGetter</CODE> in "TestMoreOSL.c" for an example. |
</OL> |
<LI>Your "getter" object primitive will not be called for |
properties other than those listed in your property table. |
Similarly, your "setter" object primitive will not be called for |
properties marked as read-only. You will be called for properties |
from classes from which you inherit properties. Typically you can |
call your super class's "getter" or "setter" object primitives to |
handle such calls (assuming your token format is compatible). |
<LI>If your callback needs to get or set standard properties of |
standard system objects (the application, or windows), you may |
find the routines in "MoreOSLHelpers.h" helpful. |
<LI>If your callback need to compare strings the same was MOSL |
does (harder than it sounds!), it can call the routines exported |
by "MoreOSLStringCompare.h". |
<LI>When implementing your "make" class event handler, you may |
want to take advantage of the <CODE>MOSLSetObjectProperties</CODE> |
routine. For an example of how to do this, look at the |
<CODE>CDocumentMake</CODE> routine in "TestMoreOSL.c". |
</UL> |
<H3>Default Event Handler</H3> |
<P>When you initialise MOSL, you pass it a class table that includes |
all of the classes of AppleScript objects in your application. When |
an Apple event arrives, MOSL first resolves the direct object and |
then calls the corresponding class event handler. This raises the |
question, what happens if the direct object isn't in the class table. |
Naively, you might expect that the event just fails. However, it is |
important to process certain events whose direct objects are never in |
the class table. For example, it is necessary for your application to |
see the "open documents" event, even though the direct object is a |
list of aliases and you don't have an alias class in your class |
table.</P> |
<P>The default event handler is the solution to this problem. If MOSL |
processes an event and can't find its direct object in the class |
table, it passes the event to the default event handler. For an |
example of how that handler should respond, see the |
<CODE>DefaultAppleEventHandler</CODE> routine in "TestMoreOSL.c". |
</P> |
<H2>Caveats</H2> |
<P>MOSL does not implement <CODE>formRelativePosition</CODE>.</P> |
<P>MOSL does not support properties, operators, and elements for |
built-in types. For example, if you ask for <CODE>length of name of |
window 1</CODE>, MOSL will fail. Notably,this also fails in the |
Finder. I consider the requirement that all applications support all |
properties, operators, and elements of built-in types to be a bug in |
AppleScript [2444537]. The accepted workaround is that the script |
developer must get the relevant property and then apply the operation |
inside AppleScript. MOSL should generate a better error number when |
it fails, however.</P> |
<P>In a similar vein, MOSL does not provide any support for |
properties or elements whose values are records or lists. In the MOSL |
philosophy, if an object has a property whose value is a record, you |
should make it a reference to another object, and if an object has a |
property whose value is a list, you should make it an element of the |
object. I've received conflicting advice as to whether this is the |
right approach. Let me know what you think.</P> |
<P>On the non-Carbon built, many of the window property accessors |
implemented in "MoreOSLHelpers.h" fail on Mac OS 8.5 through 8.6. |
This is because, on those systems, the Window Manager routine |
<CODE>GetWindowAttributes</CODE> only works for windows that were |
created using <CODE>CreateNewWindow</CODE>. The problem is manifest |
in TestMoreOSL in that the script below fails with a |
<CODE>paramErr</CODE>. I haven't had time to implement a substitute |
routine to call on those system versions. It is not a high priority |
because this routine works properly on those systems if you call it |
from Carbon, and I expect the majority of MOSL clients to be |
Carbonated.</P> |
<P><TABLE BORDER=0> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<PRE>tell application "TestMoreOSL" |
get zoomed of window 1 |
end tell</PRE> |
</TD></TR> |
</TABLE></P> |
<P>The string comparison implementation for Carbon relies on |
AppleScript's ability to coerce internation text |
(<CODE>typeIntlText</CODE>) to Unicode |
(<CODE>typeUnicodeText</CODE>). This feature was added in AppleScript |
1.3.2 (Mac OS 8.5). If your application runs under Carbon on Mac OS |
8.1, you will have to either install your own coercions handlers or |
revert to using the non-Carbon string comparison implementation.</P> |
<P>TestMoreOSL does not handle the entire <CODE>'core'</CODE> suite. |
The missing constructs, listed below, are unimplemented mostly |
because they make no sense for the test application. However, it's |
hard to guarantee that MOSL is correct and complete without |
implementing them all.</P> |
<UL> |
<LI>"saving in" parameter to "close" |
<LI>"at" parameter to "make" |
<LI>"print" event |
<LI>"delete" event |
<LI>"duplicate" event |
<LI>"move" event |
<LI>"data size" event |
<LI>"select" event |
<LI>"suite info", "event info", "class info" events -- According |
to AppleScript engineering, these events have been deprecated. |
<LI>"selection" property of "application" |
<LI>"clipboard" property of "application" |
<LI>"selection-object" class |
<LI>"alias" class |
<LI>"insertion point" class |
</UL> |
<P>MOSL chooses to return the best type for indexed elements. For |
example, if you ask TestMoreOSL for every window and there is one |
<CODE>about window</CODE> and one <CODE>node window</CODE>, you will |
get back a list <CODE>{about window id 128 of application |
"TestMoreOSL", node window id 150 of application |
"TestMoreOSL"}</CODE> as opposed to <CODE>{window id 128 of |
application "TestMoreOSL", window id 150 of application |
"TestMoreOSL"}</CODE>. This seems like the right thing to do and it |
is in line with other scriptable applications that I tested with. |
This design decision effects how I handle the <CODE>index</CODE> |
property for windows. Because I required that <CODE>index of every |
window</CODE> return a continuous range of numbers, I require that |
the <CODE>index</CODE> property be the index of the window within the |
entire window list, not the index of the window within its class of |
windows. This means that, given the above example, <CODE>index of |
node window 1</CODE> is not equal to 1. Most folks who I talk to |
think that this is a reasonable compromise.</P> |
<P>The node tree within a node window is read-only and is not |
actually stored in the document file. I may change this eventually, |
but only if it's necessary to illustrate some AppleScript concept. |
</P> |
<P>I have no yet run a leak check on MOSL. My coding style acts to |
prevent leaks, but I won't claim that MOSL is leak free until I've |
actually checked.</P> |
<P>Nested whose clauses (for example, <CODE>((nodes whose name |
contains "2") of nodes whose name contains "1") of node window |
1</CODE>) yield weird errors. I haven't had time to investigate this |
yet.</P> |
<P>MOSL does not yet implement the extra error parameters in an error |
reply event (such as the "partial results" parameter).</P> |
<H2>How it Works</H2> |
<H3>Scripting Strategies</H3> |
<P>There are two basic characteristics of any scripting |
implementation:</P> |
<UL> |
<LI>class-first versus event-first dispatching |
<LI>deep versus shallow resolution |
</UL> |
<P>MOSL implements <STRONG>object-first dispatching</STRONG>. For |
each event in the event table, MOSL registers the same Apple event |
handler (<CODE>MOSLAppleEventHandler</CODE> in file "MoreOSL.c"). |
This Apple event handler gets the direct object of the event and |
resolves the object. This results in either a single token or a list |
of tokens. For a single token, <CODE>MOSLAppleEventHandler</CODE> |
calls <CODE>DispatchEvent</CODE> to look up the token's class in the |
class table and call the corresponding class event handler. For a |
list of tokens, <CODE>MOSLAppleEventHandler</CODE> iterates over the |
list, extracting each token, calling <CODE>DispatchEvent</CODE> for |
each, and assembling the results into a list.</P> |
<P>This approach stands in contrast to <STRONG>event-first |
dispatching</STRONG>, where each Apple event is processed by a |
separate handler. In general, event-first dispatching requires more |
code, whereas object-first dispatching is more table driver. |
Object-first dispatching also maps well to the native object format |
for applications that are programmed in an object-oriented style. |
</P> |
<P>MOSL implements <STRONG>deep object resolution</STRONG>. There are |
two ways to interpret a script like <CODE>file 1 of every |
item</CODE>. In a deep resolution implementation, this produces a |
list that contains the first file of item 1, followed by the first |
file of item 2, and so on. If item X is a file, and thus doesn't |
include child files, the corresponding list item is the special value |
<CODE>missing value</CODE>. In a <STRONG>shallow object |
resolution</STRONG> implementation, this same object specifier yields |
a single object which is the first file in the list of items.</P> |
<P>Both approaches are internally consistent and various scriptable |
applications use each approach. For example, AppleScript and the |
Finder both implement shallow resolution, whereas the AppleScript |
engineering team recommends deep resolution. MOSL uses deep |
resolution not because it was easier (in fact, at various stages of |
the development process, I managed to implement both forms!) but |
because I consider it more powerful. If you look at the above |
example, you'll notice that the shallow resolution implementation |
yields the same results as <CODE>file 1</CODE>, whereas the deep |
resolution implementation yields different, and possibly more useful, |
results.</P> |
<H3>Resolution Implementation</H3> |
<P><A NAME="RecursiveResolve"></A>MOSL's object resolution is done by |
the routine <CODE>RecursiveResolve</CODE> (in "MoreOSL.c"). This |
routine's core algorithm is shown below.</P> |
<P><TABLE BORDER=0> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<PRE>on RecursiveResolve obj |
if obj is a list then |
set result to empty list |
for each element in obj |
set resolvedObj to (RecursiveResolve item) |
if resolvedObj is a list then |
append each element of resolvedObj to result |
else |
append resolvedObj to result |
end-if |
end-for |
return result |
else |
return AEResolve obj |
end-if |
end RecursiveResolve</PRE> |
</TD></TR> |
</TABLE></P> |
<P>This algorithm has a number of consequences:</P> |
<UL> |
<LI>If the incoming object specifier is a list, all elements of |
the list are resolved independently. |
<LI>The result of <CODE>RecursiveResolve</CODE> is always either a |
single object or a flat list of objects. This ensures that |
<CODE>every node of every node window</CODE> produces a flat list, |
rather than a nested list, which is in line with scripter |
expectations. |
</UL> |
<P>Whenever <CODE>AEResolve</CODE> is called, the Object Support |
Libraries invokes MOSL's various callbacks.</P> |
<UL> |
<LI><CODE>MOSLCompareProc</CODE> -- This is a standard callback |
that OSL calls to compare descriptors, both during whose clause |
resolution and in response to the <CODE>kAEEquals</CODE> Apple |
event. It is described in detail in |
<A HREF="#DescriptorComparison">Descriptor Comparison</A>. |
<LI><CODE>MOSLCountProc</CODE> -- This is a standard callback that |
OSL calls to count objects within a container. MOSL implements |
this callback in terms of the "counter" object primitive, which it |
finds in the <A HREF="#ClassTable">class table</A>. The actual |
implementation is quite straightforward. |
<LI><CODE>ClassOSLAccessorProc</CODE> -- MOSL registers this |
callback once for every class in the class table, with the refCon |
set to the class' index in the class table. The callback is |
responsible for accessing objects and properties within the |
classes in the class table. See <A HREF="#ClassOSLAccessors">Class |
OSL Accessors</A> for more details. |
<LI><CODE>PseudoClassOSLAccessorProc</CODE> -- MOSL registers this |
callback once for each pseudo-class, with a refCon that uniquely |
identifies the pseudo-class. A <STRONG>pseudo-class</STRONG> is a |
class that OSL needs to access objects within, but which isn't |
actually a class in the class table (the pseudo-class is not one |
of the application's classes). Specifically, the pseudo-classes |
accessors currently registered by MOSL are |
<CODE>typeWildCard</CODE> from <CODE>typeAEList</CODE>, |
<CODE>typeWildCard</CODE> from <CODE>typeProperty</CODE>, and |
<CODE>cFile</CODE>/<CODE>typeAlias</CODE> from |
<CODE>typeNull</CODE>. See |
<A HREF="#PseudoClassOSLAccessors">Pseudo-Class OSL Accessors</A> |
for details. |
</UL> |
<P><TABLE BORDER=0 WIDTH=498> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<P><B>Note:<BR> |
</B>Registering the <CODE>ClassOSLAccessorProc</CODE> with a |
distinct refCon for each class allows OSL to do the mapping |
from class ID to class table index using its fast built-in |
hash tables. Doing this lookup inside |
<CODE>ClassOSLAccessorProc</CODE> would either be slower or |
require me to reimplement my own fast lookup mechanism. |
</TD></TR> |
</TABLE></P> |
<H3><A NAME="ClassOSLAccessorCallback"></A>Class OSL Accessors</H3> |
<P>MOSL registers the <CODE>ClassOSLAccessorProc</CODE> object |
accessor once for each class in the class table, with the refCon set |
to the class's index in the class table. |
<CODE>ClassOSLAccessorProc</CODE> is a simple dispatcher routine. It |
establishes the class of interest (by casting its refCon to a |
<CODE>MOSLClassTableIndex</CODE>) and then examines the incoming |
<CODE>form</CODE> parameter and calls one of the following routines. |
</P> |
<P><TABLE BORDER=1 WIDTH="100%"> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formUniqueID</CODE> |
</TD><TD WIDTH=200> |
<P><CODE>ClassAccessorByUniqueID</CODE> |
</TD></TR> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formName</CODE> |
</TD><TD WIDTH=200> |
<P><CODE>ClassAccessorByName</CODE> |
</TD></TR> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formAbsolutePosition</CODE> |
</TD><TD WIDTH=200> |
<P><CODE>ClassAccessorByAbsolutePos</CODE> |
</TD></TR> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formPropertyID</CODE> |
</TD><TD WIDTH=200> |
<P><CODE>ClassAccessorByProperty</CODE> |
</TD></TR> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formRange</CODE> |
</TD><TD WIDTH=200> |
<P><CODE>ClassAccessorByRange</CODE> |
</TD></TR> |
</TABLE></P> |
<P>Of these, <CODE>ClassAccessorByUniqueID</CODE> and |
<CODE>ClassAccessorByName</CODE> are simple wrappers around the |
class's "accessByUniqueID" and "accessByName" object primitives. |
<CODE>ClassAccessorByAbsolutePos</CODE> is somewhat more complex. It |
calls the class's "counter" object primitive to determine the number |
of elements within the object, then processes the supplied index (for |
example, a positive index refers to elements counting from the |
beginning, whereas a negative index refers to elements counting from |
the end) and calls the "accessByIndex" object primitive with a |
positive index number.</P> |
<P>The most complex case for <CODE>ClassAccessorByAbsolutePos</CODE> |
is where the selection data is of <CODE>typeAbsoluteOrdinal</CODE> |
and value <CODE>kAEAll</CODE>. In this case, |
<CODE>ClassAccessorByAbsolutePos</CODE> is responsible for returning |
a token that includes all elements of the object. It does this by |
creating an <CODE>AEDescList</CODE>, repeatedly calling the |
"accessByIndex" object primitive to get the tokens for each element |
of the object, placing those tokens in the list and returning the |
list as the token.</P> |
<P><CODE>ClassAccessorByProperty</CODE> is surprisingly simple. The |
incoming parameter is a token that represents an object. [Attempts to |
get properties from properties are handled by the |
<A HREF="#PseudoClassOSLAccessors">pseudo-class accessor</A> for |
<CODE>typeProperty</CODE>.] The result must be a token that |
represents a property. All the routine needs to do is change the |
<CODE>tokType</CODE> field of the |
<CODE><A HREF="#TokenFormat">MOSLToken</A></CODE> structure to |
<CODE>typeProperty</CODE> and the <CODE>tokPropName</CODE> field to |
be the property name (four character code). It also checks that the |
class actually includes the property name in its property table. This |
ensures that object resolution for bogus property names comes to a |
quick halt with a meaningful error code.</P> |
<P><CODE>ClassAccessorByRange</CODE> is surprisingly complex. The |
basic algorithm is shown below.</P> |
<P><TABLE BORDER=0> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<PRE>on ClassAccessorByRange containerObject, selectionData, desiredType |
get bounds1 from selectionData using keyAERangeStart |
get bounds2 from selectionData using keyAERangeStop |
AEResolve bounds1 |
AEResolve bounds2 |
coerce bounds1 to its base type |
coerce bounds2 to its base type |
set state to kLookingForFirst |
for each element in containerObject |
coerce theElement to base type |
if state is kLookingForFirst then |
if theElement is bounds1 then |
set state to kLookingForSecond |
else if theElement is bounds2 then |
set state to kLookingForSecond |
set bounds2 to bounds1 |
end-if |
end-if |
if state is kLookingForFirst then |
if theElement is compatible with desiredType |
add theElement to result |
end-if |
if theElement is bounds2 then |
set state to kDone |
end-if |
end-if |
if state = kDone then |
break |
end-if |
end-for |
end ClassAccessorByRange</PRE> |
</TD></TR> |
</TABLE></P> |
<P>In words, this algorithm is:</P> |
<BLOCKQUOTE><P>Resolve the two boundary objects and coerce them to |
their base class. Iterate over each element of the container object. |
When you find the first boundary object, start adding compatible |
elements to the result. When you find the secondary boundary object, |
leave.</P></BLOCKQUOTE> |
<P>This whole process is implemented in terms of the class's |
"counter", "accessByIndex", and "coerceToken" object primitives.</P> |
<H3><A NAME="PseudoClassOSLAccessors"></A>Pseudo-Class OSL Accessors |
</H3> |
<P>The MOSL routine <CODE>PseudoClassOSLAccessorProc</CODE> is |
registered as an OSL object accessor three times, which it |
distinguishes between by consulting its refCon.</P> |
<UL> |
<LI><CODE>cFile</CODE>/<CODE>typeAlias</CODE> from |
<CODE>typeNull</CODE> (implemented by |
<CODE>PseudoCFileAccessor</CODE>) |
<LI><CODE>typeWildCard</CODE> from <CODE>typeProperty</CODE> |
(implemented by <CODE>PseudoCPropertyAccessor</CODE>) |
<LI><CODE>typeWildCard</CODE> from <CODE>typeList</CODE> |
(implemented by <CODE>PseudoCListAccessor</CODE>) |
</UL> |
<P>The first case is covered in the <A HREF="#cFileIssues">cFile |
Issues</A> section. This section describes the other two cases.</P> |
<H4>Property Pseudo-Class Accessor</H4> |
<P>When OSL asks for a property from an object, the |
<CODE>ClassOSLAccessorProc</CODE> returns a token of |
<CODE>typeProperty</CODE>. When the script wants to get the value of |
a property, this token becomes the direct object of the "get data" |
event. However, if the value of the property is itself an object |
specifier (for example, in TestMoreOSL, every document has a |
<CODE>node display</CODE> property that is a reference to the node |
window for that document), it is possible for an object specifier to |
ask for properties or elements of the property.</P> |
<P>The object specifier <CODE>node 1 of node display of document |
1</CODE> (in the TestMoreOSL application) is an example of this |
situation. As OSL tries to resolve this object specifier, the first |
thing it does is request document 1 of the application. The |
<CODE>ClassOSLAccessorProc</CODE> handles this, returning a document |
token (<CODE>tokType</CODE> is <CODE>cDocument</CODE>). OSL then |
requests the "node display" property of this token. Again, the |
<CODE>ClassOSLAccessorProc</CODE> handles this, returning a property |
token (<CODE>tokType</CODE> is <CODE>typeProperty</CODE>, |
<CODE>tokObjType</CODE> is <CODE>cDocument</CODE>). Finally, OSL |
attempts to resolve node 1 of this property token. The token type is |
<CODE>typeProperty</CODE>, so it is at this point that the |
<CODE>PseudoCPropertyAccessor</CODE> is called.</P> |
<P><CODE>PseudoCPropertyAccessor</CODE> handles requests for elements |
and properties of properties. The first thing it does is use the |
token's <CODE>tokObjType</CODE> field to work out what class of |
object the token is a property for. It then calls the class's |
"getter" object primitive on the token. The specification for this |
primitive is that, if the token is a property that is a reference to |
another object, it should return a token for that object. |
<CODE>PseudoCPropertyAccessor</CODE> now has a token for the object |
referenced by the property. It then calls |
<CODE>AECallObjectAccessor</CODE> on that token to recommence |
resolution based on the object referenced by the property.</P> |
<P>The end result is that MOSL resolves objects and properties in |
object reference properties with a minimum of fuss for the client |
application.</P> |
<H4>List Pseudo-Class Accessor</H4> |
<P>MOSL regularly uses <CODE>typeAEList</CODE> as a token type. For |
example, if you ask for every document, MOSL will return the list as |
of documents as a <CODE>typeAEList</CODE>, where each element is a |
document token (<CODE>tokType</CODE> is <CODE>cDocument</CODE>). This |
approach is fairly standard (it is recommended in <CITE>Inside |
Macintosh: Interapplication Communication</CITE>) but it does have |
some consequences on for MOSL's object accessors. Specifically, |
because MOSL uses <CODE>typeAEList</CODE> as a token type, MOSL must |
provide an object accessor for <CODE>typeAEList</CODE>. The |
<CODE>PseudoClassOSLAccessorProc</CODE> routine (which is simply a |
wrapper for <CODE>PseudoCListAccessor</CODE>) is that accessor.</P> |
<P>As an example of why this is necessary, consider the object |
specifier <CODE>name of every document</CODE>. The |
<CODE>ClassOSLAccessorProc</CODE> resolves the first part of the |
object specifier (<CODE>every document</CODE>) to a list |
(<CODE>typeAEList</CODE>) of document tokens |
(<CODE>cDocument</CODE>). OSL then attempts to access the "name" |
property of this list. It is not smart enough to do this itself, so |
MOSL must register an object accessor for <CODE>typeAEList</CODE>. |
</P> |
<P>The basic implementation of the list pseudo-class object accessor |
is shown below.</P> |
<P><TABLE BORDER=0> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<PRE>on PseudoCListAccessor containerObject, selectionData, desiredType |
set result to empty list |
for each element in containerObject |
set resultItem to (AECallObjectAccessor item,selectionData,desiredType) |
if resultItem is a list then |
append each element of resultItem to result |
else |
append resultItem to result |
end-if |
end-for |
return result |
end PseudoCListAccessor</PRE> |
</TD></TR> |
</TABLE></P> |
<P>The idea is that, if the container object is a list, |
<CODE>PseudoCListAccessor</CODE> breaks the list down into elements, |
calls the appropriate object accessor for each element, and puts the |
results into a list which it then returns. It uses the same list |
merging technique as |
<CODE><A HREF="#RecursiveResolve">RecursiveResolve</A></CODE> to |
ensure that the resulting list is always flat.</P> |
<P>This implementation works just fine |
<CODE>formAbsolutePosition</CODE> and <CODE>formPropertyID</CODE>. In |
fact, this is the core of the deep resolution implementation. For |
example, an object specifier of <CODE>node 1 of every node |
window</CODE> would cause <CODE>PseudoCListAccessor</CODE> to be |
called, and it would redispatch the request for node 1 to each |
element of the list and reassemble the results into a list. However, |
this basic implementation does not work well for |
<CODE>formRange</CODE>. The exact problem and solution are too |
convoluted to explain here, but are covered in the comments entitled |
"formRange for typeAEList" in the file "MoreOSL.c".</P> |
<P>There are two other saliant points in the implementation of |
<CODE>PseudoCListAccessor</CODE>.</P> |
<UL> |
<LI>If the call to <CODE>AECallObjectAccessor</CODE> fails, the |
special value <CODE>missing value</CODE> is placed in the result. |
For example, the result of the following script is a list whose |
second element is missing value, because node 2 of node window 1 |
does not have any children. <TABLE BORDER=0> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<PRE>tell application "TestMoreOSL" |
node 1 of every node of node window 1 |
end tell</PRE> |
</TD></TR> |
</TABLE> |
<LI>If the resolution request passed to |
<CODE>PseudoClassOSLAccessorProc</CODE> is for |
<CODE>formRange</CODE> and the <CODE>desiredClass</CODE> is |
<CODE>cObject</CODE>, <CODE>PseudoClassOSLAccessorProc</CODE> does |
not call <CODE>PseudoCListAccessor</CODE>; instead it just returns |
the N'th descriptor in the list. This weird special case is |
required to make AppleScript's <CODE>repeat with</CODE> construct |
work properly [2445795]. |
</UL> |
<H3><A NAME="cFileIssues"></A>cFile Issues</H3> |
<P>I encountered a number of weird problems related to |
<CODE>cFile</CODE> while working on MOSL. Most of them have |
workarounds, but the problems are themselves worth noting.</P> |
<UL> |
<LI>Once you add the "file" property to your |
<CODE>cDocument</CODE> class, you must also add a |
<CODE>cFile</CODE> class to your suite. The <CODE>cFile</CODE> |
class can be empty, but it has to be there to make AppleScript |
compile properly. See the comments in "TestMoreOSLTerminology.r" |
for more details. [2444517] |
<LI>Even after you add the <CODE>cFile</CODE> class, the construct |
<CODE>every document whose file is alias "Macintosh |
HD:Test"</CODE> still doesn't work properly. See the comments in |
"TestMoreOSLTerminology.r" for more details. [2444528] |
<LI>The only way for a scripter to save a document to a file that |
doesn't yet exist is to use the construct <CODE>save document 1 in |
file "Macintosh HD:New File Name"</CODE>. This actually generates |
a <CODE>cFile</CODE> object specifier that confused MOSL because |
<CODE>cFile</CODE> is not in the class table. I resolved this |
problem by adding <CODE>cFile</CODE> to the set of pseudo-classes |
handled by <CODE>PseudoClassOSLAccessorProc</CODE> (which passes |
the actual work to <CODE>PseudoCFileAccessor</CODE>). When |
<CODE>PseudoCFileAccessor</CODE> is called to return a |
<CODE>cFile</CODE> object object within a container, it simply |
reassembles the form, selection data, and container information |
into an object specificer and then calls <CODE>AECoerceDesc</CODE> |
to coerce that object specifier to an <CODE>FSSpec</CODE>; the |
coercion is handled by a built-in coercion handler. |
</UL> |
<H3><A NAME="DescriptorComparison"></A>Descriptor Comparison</H3> |
<P>MOSL registers the routine <CODE>MOSLCompareProc</CODE> as its OSL |
comparison callback. When I started MOSL, I was under the impression |
that this routine would only be called when OSL needed to compare two |
of my tokens; because OSL has no idea of my token format, it has to |
call me to do the job. This is not true. OSL calls the comparison |
callback whenever it needs to compare anything, be they object |
tokens, properties, or just data. OSL should, in my opinion, be |
smarter about this [2444551], but for the moment the MOSL comparison |
callback has to handle all of these comparisons explicitly.</P> |
<P><CODE>MOSLCompareProc</CODE> has two basic comparison methods. |
</P> |
<UL> |
<LI>compare data descriptors (implemented by |
<CODE>CompareDataDescriptors</CODE>) |
<LI>compare tokens (implemented as part of |
<CODE>MOSLCompareProc</CODE>) |
</UL> |
<P>The implementation of each method is fairly straightforward |
(comparing data descriptors is longwinded, but simple, except for |
<A HREF="#MoreOSLStringCompare">strings</A>). The tricky part of |
<CODE>MOSLCompareProc</CODE> is deciding which method to use. The |
basic algorithm is:</P> |
<OL> |
<LI>If both sides are objects, compare the tokens |
<LI>If either side is a property, get the value of the property, |
coerce the other side to the same type, and compare the data. |
<LI>If either side is data, get the data, coerce the other side to |
the same type, and compare as data. |
</OL> |
<P>The comments inside <CODE>MOSLCompareProc</CODE> ("MoreOSL.c") |
explain this in greater detail.</P> |
<H3>Debugging Infrastructure</H3> |
<P>Debugging MOSL was hard. This was partly because of my |
inexperience with AppleScript and OSL, but also because the |
combination of OSL and MOSL is a big object-oriented framework, and |
debugging big object-orient frameworks is inherently hard. For |
example, when you step over <CODE>AEResolve</CODE>, you have no real |
idea which of your callbacks will be called and when.</P> |
<P>My primary solution was logging. The MoreBBLog module allows easy |
logging to BBEdit, and MOSL makes extensive use of that facility. |
MOSL also registers coercion handles from all common types to |
<CODE>typeText</CODE>. This allows MoreBBLog to display more |
meaningful information in the log. Most of those coercion handlers |
are in "MoreBBLog.c" itself, but MoreOSL also registers a coercion |
handler (<CODE>DebugTokenCoerceProc</CODE>) to coerce tokens to text. |
</P> |
<P>MOSL's logging is controlled by four bits in the |
<CODE>gDebugFlags</CODE> global variable.</P> |
<UL> |
<LI><CODE>kMOSLLogOSLMask</CODE> -- When set, this bit causes MOSL |
to log all of its interactions with OSL. Specifically, all of |
MOSL's OSL callback routines record that they have been called, |
their parameters, and their results. |
<LI><CODE>kMOSLLogCallbacksMask</CODE> -- When set, this bit |
causes MOSL to log whenever it calls a client callback (class |
event handler or object primitive). This output is primarily aimed |
at folks who use MOSL and want to figure out what their callbacks |
are doing wrong. |
<LI><CODE>kMOSLLogGeneralMask</CODE> -- When set, this bit causes |
MOSL's general class event handlers to log their actions. |
<LI><CODE>kMOSLLogDispatchMask</CODE> -- When set, this bit causes |
MOSL's core Apple event dispatcher to log its actions. |
</UL> |
<P>By default the debug version of TestMoreOSL has |
<CODE>kMOSLLogOSLMask</CODE> and <CODE>kMOSLLogDispatchMask</CODE> |
set, but it also exposes these flags through its <CODE>debug</CODE> |
property so that a script can control the level of logging. The |
standard test script uses this technique to enable logging when you |
run an individual test but disable it when you run all of the tests, |
on the assumption that individual tests are run while debugging and |
all tests are run during regression testing.</P> |
<P>The standard test script ("Test Script") proved to be an |
invaluable debugging tool in itself. The best feature of the test |
script is that it allowed me to make significant changes to the |
implementation (such as a conversion from Pascal to C -- a long |
story) with some assurance that I hadn't broken some obsure part of |
the implementation.</P> |
<P>Another very useful debugging feature is the |
<CODE>DebugOSLAccessorProc</CODE>. In the debug version of MOSL, I |
register this object accessor as being capable of accessing anything |
from anything. If OSL attempts to access some object from a class I |
wasn't expecting, <CODE>DebugOSLAccessorProc</CODE> runs, logs the |
parameters, and returns an error. This way I can quickly spot failed |
object accesses in the log.</P> |
<P>MOSL use asserts everywhere. This has helped me find numerous |
bugs.</P> |
<H3><A NAME="MoreOSLStringCompare"></A>MoreOSL String Comparison |
</H3> |
<P>Implementing support for AppleScript's string comparison operators |
is easy to do badly, but very hard to do right. The challenges |
include:</P> |
<UL> |
<LI>The system provides no international friendly way of |
implementing the "contains" operator that works 100% correctly. |
The CFString mechanism works pretty well, but it does have its |
problems (see below). |
<LI>Any mechanism provided by the system still wouldn't be the |
same as AppleScript's built-in string operators because |
AppleScript carries around its own string canonicalisation tables. |
<LI>There is no documented way of getting at the script's current |
"considering" and "ignoring" state. This is a known limitation, |
documented in the AppleScript Language Guide, but it's still lame. |
</UL> |
<P>Some of these difficulties are evident even in the Roman script |
system. For example, assuming document 1 is called "Þnd" (the |
first character is the fi ligature (option-shift-5)), the following |
script snippets might produce different results because the |
application implements string comparison in terms of |
<CODE>IdenticalText</CODE> or <CODE>CFStringCompare</CODE>, which |
break down the ligature, but AppleScript's built-in string tables |
doesn't (even if you turn on expansion).</P> |
<P><TABLE BORDER=0> |
<TR> |
<TD bgcolor="#EEEEEE"> |
<PRE>tell application "X" |
name of document 1 = "find" |
end tell |
|
tell application "X" |
get name of document 1 |
result = "find" |
end tell</PRE> |
</TD></TR> |
</TABLE></P> |
<P>Moreover, while testing this against CFString I discovered a bug |
[2442526] that causes CFString to break the identity:</P> |
<BLOCKQUOTE><P>(a equals b) implies ((a contains b) and (b contains |
a))</P></BLOCKQUOTE> |
<P>While this probably won't be a huge problem in practice, it is |
annoying.</P> |
<P>While casting around for a solution (especially for the "contains" |
operator) I looked at how some other applications did this. That was |
a depressing exercise. The Finder's implementation of "contains", for |
example, does not even attempt to be two-byte friendly, nor does it |
address the possibility that <CODE>IdenticalText</CODE> may return |
true for text of different length (for example, "Þnd" vs |
"find").</P> |
<P>Despite these obstacles, an application must implement string |
comparison in order to support the simplest AppleScript constructs |
(such as <CODE>formName</CODE>). So I had to come up with a solution. |
I chose two different paths depending on the target environment.</P> |
<UL> |
<LI>Carbon -- For Carbon applications, CFString was the obvious |
solution, despite the niggling bug described above. |
<LI>Non-Carbon -- For non-Carbon applications, there is no |
obvious, clean solution. Eventually I resorted to loading an |
AppleScript (using OSA) and forcing it to do the comparison (using |
<CODE>OSADoEvent</CODE>). Sleasy, but functionaly. |
</UL> |
<P>After this much pain, I thought it was important to file an |
enhancement request [2444555] asking for a better solution.</P> |
<H2>Credits and Version History</H2> |
<P>If you find any problems with this sample, mail |
<DTS@apple.com> as the first line of your mail and I'll try to |
fix them up.</P> |
<P>1.0b1 (Mar 2000) was the first version released for internal |
review.</P> |
<P>1.0b2 (Apr 2000) was the first release distributed to the general |
public. Contains a small number of minor fixes. The biggest |
functional change is that Apple events defined to have no reply (such |
as <CODE>'odoc'</CODE>) no longer show "current application" as their |
reply in Script Editor's log.</P> |
<P>Share and Enjoy.</P> |
<P>Apple Developer Technical Support<BR> |
Networking, Communications, Hardware (slumming in scriptland!)</P> |
<P>25 Apr 2000</P> |
<P> </P> |
</BODY> |
</HTML> |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14