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.
MIB-Libraries/MoreOSL/ReadMe.html
<HTML> |
<HEAD> |
<TITLE>Read Me</TITLE> |
</HEAD> |
<BODY BGCOLOR="#FFFFFF"> |
<H1>Read Me About MoreOSL</H1> |
<P>1.0b3</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> |
<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> |
<LI>modern -- MOSL incorporates modern AppleScript techniques, |
such as 'deep' object resolution. It is also fully Carbon |
compatible.</LI> |
<LI>well tested -- MOSL includes a test application, TestMoreOSL, |
that demonstrates its capabilities. It also includes a large suite |
of AppleScript-based tests.</LI> |
<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.</LI> |
</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> |
<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> |
<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> |
<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> |
<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> |
<LI>"TestMoreOSL" -- A folder containing the MOSL test |
application, TestMoreOSL.</LI> |
</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.</P> |
</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> |
<LI>If you launch TestMoreOSL and then close all the windows in |
BBEdit, the logging will be generated but won't be displayed.</LI> |
<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> |
</LI> |
</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 6 compiler with Universal Interfaces |
3.4b4). 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> |
<P>The sample also builds with Project Builder on Mac OS X 1.0. To |
build it, open the "TestMoreOSL.pbproj" project and choose Build from |
the Build menu.</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> |
<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> |
<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> |
<LI>The event table doesn't actually contain pointers to event |
handlers. The actual event handlers are supplied as part of the |
class table.</LI> |
</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> |
<LI>A pointer to a <A HREF="#PropertyTable">property |
table</A>.</LI> |
<LI>A pointer to a <A HREF="#ClassEventHandlers">class event |
handlers table</A>.</LI> |
<LI>A set of <A HREF="#ObjectPrimitives">object |
primitives</A>.</LI> |
</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 NULL. 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> |
<LI>"setter" is required if the class has any properties that can |
be modified.</LI> |
<LI>"counter" and "accessByIndex" are required if objects of the |
class have elements.</LI> |
<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> |
<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> |
<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.</LI> |
</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> |
<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> |
<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> |
<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> |
<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.</LI> |
</OL> |
</LI> |
<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> |
<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> |
<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> |
<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".</LI> |
</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> |
<LI>"at" parameter to "make"</LI> |
<LI>"print" event</LI> |
<LI>"delete" event</LI> |
<LI>"duplicate" event</LI> |
<LI>"move" event</LI> |
<LI>"data size" event</LI> |
<LI>"select" event</LI> |
<LI>"suite info", "event info", "class info" events -- According |
to AppleScript engineering, these events have been |
deprecated.</LI> |
<LI>"selection" property of "application"</LI> |
<LI>"clipboard" property of "application"</LI> |
<LI>"selection-object" class</LI> |
<LI>"alias" class</LI> |
<LI>"insertion point" class</LI> |
</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> |
<LI>deep versus shallow resolution</LI> |
</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> |
<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.</LI> |
</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> |
<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> |
<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> |
<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.</LI> |
</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.</P> |
</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></P> |
</TD> |
<TD WIDTH=200> |
<P><CODE>ClassAccessorByUniqueID</CODE></P> |
</TD> |
</TR> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formName</CODE></P> |
</TD> |
<TD WIDTH=200> |
<P><CODE>ClassAccessorByName</CODE></P> |
</TD> |
</TR> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formAbsolutePosition</CODE></P> |
</TD> |
<TD WIDTH=200> |
<P><CODE>ClassAccessorByAbsolutePos</CODE></P> |
</TD> |
</TR> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formPropertyID</CODE></P> |
</TD> |
<TD WIDTH=200> |
<P><CODE>ClassAccessorByProperty</CODE></P> |
</TD> |
</TR> |
<TR> |
<TD WIDTH=150> |
<P><CODE>formRange</CODE></P> |
</TD> |
<TD WIDTH=200> |
<P><CODE>ClassAccessorByRange</CODE></P> |
</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 <A HREF="#TokenFormat"><CODE>MOSLToken</CODE></A> |
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>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.</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> |
<LI><CODE>typeWildCard</CODE> from <CODE>typeProperty</CODE> |
(implemented by <CODE>PseudoCPropertyAccessor</CODE>)</LI> |
<LI><CODE>typeWildCard</CODE> from <CODE>typeList</CODE> |
(implemented by <CODE>PseudoCListAccessor</CODE>)</LI> |
</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 <A HREF="#RecursiveResolve"><CODE>RecursiveResolve</CODE></A> |
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> |
<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].</LI> |
</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> |
<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> |
<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.</LI> |
</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> |
<LI>compare tokens (implemented as part of |
<CODE>MOSLCompareProc</CODE>)</LI> |
</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> |
<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> |
<LI>If either side is data, get the data, coerce the other side to |
the same type, and compare as data.</LI> |
</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> |
<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> |
<LI><CODE>kMOSLLogGeneralMask</CODE> -- When set, this bit causes |
MOSL's general class event handlers to log their actions.</LI> |
<LI><CODE>kMOSLLogDispatchMask</CODE> -- When set, this bit causes |
MOSL's core Apple event dispatcher to log its actions.</LI> |
</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> |
<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> |
<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.</LI> |
</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>(a equals b) implies ((a contains b) and (b contains a))</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> |
<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.</LI> |
</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>1.0b3 (Feb 2001) is the first cut at a Mac OS X release. The |
sample now includes a Project Builder project. The code more-or-less |
works, although there are still minor things left to tweak (such as |
the icons). I have not yet run the test script to test the entire |
code base on Mac OS X.</P> |
<P>Share and Enjoy.</P> |
<P>Apple Developer Technical Support<BR> |
Networking, Communications, Hardware (slumming in scriptland!)</P> |
<P>15 Feb 2001</P> |
<P> </P> |
</BODY> |
</HTML> |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-10-27