Resource Templates
Volume Number: 14 (1998)
Issue Number: 9
Column Tag: Tools Of The Trade
Resource Templates
by Doug McKenna, Mathemæsthetics, Inc
Edited by the MacTech Editorial Staff
Programming on the Macintosh involves creating and managing a variety of complicated data structures that describe everything from a basic window to the preferences data structure of our specific applications. As Macintosh programmers we rely on resource editors, like Apple's ResEdit and Mathemæsthetics Resorcerer to view and manipulate many of those data structures. In turn, those resource editors rely on TMPL type resources to define and record the structure of all other types of resources. In this article, Doug McKenna (the author of the high performance resource editor Resorcerer) presents an introduction to TMPL resources. - The Editor
What is a Resource Template?
Resorcerer contains a powerful 32-bit data editor that parses blocks of data of a given resource type into the data's constituent editable fields. It does this by following a set of instructions that declares the sequential format of the data. Each set of instructions is called a template, and each template is itself kept in a resource of type 'TMPL'. The binary data format of the template resource is quite simple, and can itself be described by another 'TMPL'. Editing templates, however, is such a common and important activity that there is a dedicated editor within Resorcerer designed specifically for dealing with 'TMPL' resources.
Each 'TMPL' resource has the same internal structure for its own data, but the data itself describes different data formats. The first four characters of the 'TMPL' resource's name, when taken as a resource type, uniquely identify the resource type that the template decribes. Resorcerer pays attention to templates it finds installed in any of several folders next to the application which are reserved for keeping common or private templates; or in resource files that it has opened for editing; or within Resorcerer's own application resources (Figure 1). 'TMPL' resources whose names are less than 4 ASCII characters in length are ignored. 'TMPL' resource IDs are also ignored.
Figure 1. Template Resources in a File.
The data within each template is nothing more than an arbitrarily long list of data parsing instructions, also called fields. Each field consists of two parts: (1) a variable-length Pascal-style string, followed by (2) a four-byte parsing code. Instructions are packed one after the other; there are no pad or alignment bytes anywhere. There is also no initial count, so that the number of instructions in the template can only be determined by parsing the thing until the end of the template data is reached (Figure 2).
Figure 2. TMPL Structure.
The string is called the instruction's label, and, being Pascal, consists of an unsigned length byte, followed by that many bytes of character data. The label primarily serves to identify for the user of the Data Editor the meaning of each parsed data field. Occasionally, the label has certain special characteristics for the benefit of the Data Editor's parser/formatter/interface (for instance, if the label is "Reserved", "Filler", or "Unused", the editor doesn't allow the user to edit the field), but in general the label is equivalent to a comment in a compiled language, and can be set to whatever you like. For instance, in the Japanese version of Resorcerer, the field labels are translated into Kanji.
The four-byte parsing code is called the field's type. It usually encodes both the size and manner of presentation of the next data field to be parsed sequentially from the data; however, there are a variety of special purpose codes that guide Resorcerer's parser in higher-level ways, or which are used to enhance the interactive interface of the Data Editor.
Resorcerer is Upwardly Compatible with ResEdit Templates
Other types of template resources have been designed, notably for MacsBug ('mxwt' resources) and Metrowerks' Constructor ('CTYP' resources), both of which can in turn be described by Resorcerer templates. The descriptive capabilities of Rez templates, which are kept in source code text files rather than resources, are more powerful, but can only be used for compiling.
The simple and elegant 'TMPL' structure described above was first implemented in Apple's resource editor, ResEdit, over a dozen years ago. Although you can edit a minimal set of simple Macintosh resources with ResEdit templates, in the ensuing years Apple and its developers have created much larger and more complicated data structures, which ResEdit cannot support. This is due primarily to reliance on compiling resources with Rez, but also from using the enhanced features that Resorcerer has provided over the years.
ResEdit supports 36 template field parsing codes; Resorcerer supports 128 (or more, if you count a handful of synonym field types implemented for mnemonic consistency). Fortunately, though, 34 of those 128 operate exactly the same as far as properly parsing data built with ResEdit, and the remaining 2 types (Booleans and fixed-length Pascal string buffers) are configurable to behave compatibly or not. Thus if you've invested time in creating your own ResEdit templates and built resources with them, Resorcerer's Data Editor can use your templates unchanged, and you can still edit the resources you or others have previously created.
Differences Among Data Descriptions
To see the differences among various ways of describing a well known resource, consider the different ways that the dialog window ('DLOG') resource can be described using Rez, ResEdit, Resorcerer, and C. The data format is simple enough: a few consecutive numeric fields, followed by a Pascal string for the window title, followed by another numeric field.
Listing 1: Four ways to describe a 'DLOG'
Rez Template
type 'DLOG' {
rect;
integer documentProc, dBoxProc, plainDBox, altDBoxProc,
noGrowDocProc, movableDBoxProc, zoomDocProc = 8,
zoomNoGrow = 12, rDocProc = 16;
byte invisible, visible;
fill byte;
byte noGoAway, goAway;
fill byte;
unsigned hex longint;
integer;
pstring Untitled = "Untitled";
#if SystemSevenOrLater
align word;
unsigned integer noAutoCenter = 0x0000,
centerMainScreen = 0x280a,
alertPositionMainScreen = 0x300a,
staggerMainScreen = 0x380a,
centerParentWindow = 0xa80a,
alertPositionParentWindow = 0xb00a,
staggerParentWindow = 0xb80a,
centerParentWindowScreen = 0x680a,
alertPositionParentWindowScreen = 0x700a,
staggerParentWindowScreen = 0x780a;
#endif
};
------------------------------------
ResEdit TMPL
RECT BoundsRect
DWRD ProcID
BOOL Visible
BOOL GoAway
DLNG RefCon
DWRD Items ID
PSTR Title
AWRD
HWRD Auto Position
------------------------------------
Resorcerer TMPL
RECT BoundsRect
WB12 'WDEF' resource ID
CASE Standard system=0
CASE Standard desk accessory=1
WB04 Variation code
CASE Document/standard accessory=0
CASE Modal dialog=1
CASE Plain frame=2
CASE Shadow frame=3
CASE Non-growable document=4
CASE Moveable modal dialog=5
CASE Zoomable document=8
CASE No grow, zoomable document=12
BOOL Visible
BOOL GoAway
DLNG RefCon
RSID Item list ('DITL') resource ID
ESTR Title
HWRD Window placement
CASE Leave it alone=$0000
CASE Center on main screen=$280A
CASE Center in parent window=$A80A
CASE Center on parent's screen=$680A
CASE Alert on main screen=$300A
CASE Alert in parent window=$B00A
CASE Alert in parent's screen=$700A
CASE Stagger on main screen=$380A
CASE Stagger in parent window=$B80A
CASE Stagger on parent's screen=$780A
------------------------------------
C struct
#pragma options align=mac68k
typedef struct {
Rect boundsRect;
short procID;
char visible;
char filler1;
char goAwayFlag;
char filler2;
long refCon;
Str255 title;
short positionCode;
} WINDResource;
#pragam options align=reset
Note that the C struct is not an accurate description of the resource data format, because the size of the title in the resource data is variable, whereas in the above a fixed-length Str255 buffer of maximum size of 256 bytes has been declared. Because of this, you can't cast the resource data handle to (WINDResource **) and expect the positionCode field to have the correct value in it.
Also worth noting is that both the Resorcerer and Rez descriptions include symbolic definitions of various field constants of interest directly in the format declaration; whereas the ResEdit and C descriptions don't.
The Rez definition of a 'DLOG' actually defines two data structures, depending on whether the constant SystemSevenOrLater is defined to be 1 or not.
Creating a New Template
To create a new template in Resorcerer, launch the resource editor, choose New File (CMD-N) and then choose New Resource (CMD-N) to specify the 'TMPL'. The resource ID can be anything; you can set the resource name to any four-character type you want the template to describe (for instance, 'test'). This brings up Resorcerer's Template Editor, which initally displays nothing since there are no fields defined yet in your new template. You can then choose New Field (CMD-N) from the editor's Template menu (or double-click on the field insertion caret's triangle) and enter the field data parsing type code and its label string. The field window provides you with a complete listing of all possible 4-character field types and their meanings, in case you're not sure what the four-character code is.
The Template Editor in Resorcerer displays and edits 'TMPL' resources (Figure 3); the Data Editor then uses the template to parse your resource as a data structure declared by the template (Figure 4). When a resource is selected in Resorcerer's File Window, and the Data button is enabled, this means that a template for that resource type is available for the Data Editor to use.
Figure 3. Editing a TMPL Field.
Figure 4. The Data Editor uses a TMPL to edit an 'ldes' resource.
Once you're familiar with the various template field types and operation, it is usually easier to type the list of field types and labels into a text file and then copy and paste them all at once into the Template Editor's window. You can do the reverse also: select any set of fields, choose Copy, and you can paste the template fields into a text file (Figure 5).
Figure 5. Editing a TMPL as text.
Resorcerer Template Field Types
The template field types that Resorcerer supports can parse and format many categories of data. The newer types were defined over the years as needed, based on what developers, including myself, were doing in various resource data structures, or for ease-of-use interface reasons in the Data Editor's operation, or for consistency. Since each "opcode" is allowed only 4 characters, it has been somewhat of a struggle to ensure some form of consistency in the type mnemonics. For instance, in general, an initial 'B' stands for "byte", a 'W' stands for a 2-byte "word" (left-over from 68k terminology, not PPC terminology), and 'L' stands for a 4-byte long. 'H' stands for hex, 'U' for unsigned, 'D' for signed decimal, 'P' for Pascal, and 'C' for C (how succinct!). 'S' stands for some form of Sized. In the context of delimiting groups of fields, the suffix 'B' stands for "begin" and 'E' stands for "end". I have studiously avoided using any lower-case characters, as this would add a whole degree of freedom to the difficulty of remembering what code is what.
In the following headings, the Resorcerer-specific extensions to the original ResEdit types are in boldface, and I've added some anecdotes explaining how some of these newer types came about.
Bits, bit fields, and boolean flags
BBIT, WBIT, LBIT, BBnn, WBnn, LBnn, BFLG, WFLG, LFLG, BOOL
The BBIT, WBIT, and LBIT fields each parse one bit out of the current byte, 2-byte word, or 4-byte long. Bits are parsed from most significant to least significant. Each of these is the same as a boolean field in a Rez template, but when Resorcerer's Data Editor displays the data, it numbers the bits appropriately depending on the field type: 7 to 0 for BBITs, 15 to 0 for WBITs, and 31 to 0 for LBITs. The bit-numbering is reversed if little-ending parsing mode is in effect.
The BBnn, WBnn, and LBnn fields operate similarly, except that instead of parsing just 1 bit, they each parse the next nn bits, where nn is a 2-digit ASCII decimal number encoded directly into the field type (sort of an immediate argument for the parsing opcode). Thus, WB01 is the same as WBIT; LB14 parses 14 bits from within a 32-bit long; etc. Each of these is the same as a boolean[] array in Rez.
You can intermix the single-bit with multi-bit parsing fields in series, as long as the total number of bits always exactly reaches a byte, word, or long boundary, depending on whether you are using the 'B...', 'W...', or 'L...' variant. The Data Editor performs no implicit hidden padding in any way, and will complain if a multi-byte field crosses an appropriate storage boundary. For example, the two declarations
Listing 2: Declarations
------------------------------------
Bits declared in C
unsigned long sizeCode : 2,
localChannel : 4,
isExtinct : 1,
isPurple : 1,
isDinosaur : 1,
isObnoxious : 1,
groupCount : 21;
------------------------------------
Bits in Resorcerer TMPL
LB02 Size code
LB04 Local channel
LBIT Is extinct?
LBIT Is a dinosaur?
LBIT Is purple?
LBIT Is obnoxious?
LB21 Number of newsgroups
LBIT Filler
are basically the same, with the exception that in C the missing low-order bit is silently added by the compiler, whereas in a TMPL, you have to explicitly declare everything: the total number of consecutive 'WB' bits has to add up to a multiple of 16.
I originally implemented the various word and long extensions to the BBIT field type because documentation for various bit flags routinely refers to them by bit number (e.g. bit 24 in this long field), and I couldn't stand sitting in front of multi-thousand dollar computing machine, forced to count bits in order to get things just right.
The BFLG, WFLG, and LFLG fields are designed to correspond to how a single boolean flag has historically been coded in C, which is to say, as the value 0 or 1 stored in the low-order bit of a byte, short, or long integer. The higher order bits are parsed automatically by these three field types, but those bits are not editable and are set to 0. For instance, BFLG is the same as [BB07, BBIT].
The BOOL field actually parses 2 bytes. The value of the field is TRUE if it exactly matches the 16-bit value kept in Resorcerer's own 'BOOL' 128 resource, which is set to 0x0100. The BOOL field type was implemented originally in ResEdit to take care of the hidden padding problem that occurs in C structs and Pascal records. Boolean fields are typed to be single bytes, but because of this, the 68k compilers would add a hidden pad byte after the Boolean field. So a BOOL field is TRUE if its integer value is 256, and FALSE if it's 0. I was never able to find any documentation on its behavior. After watching what ResEdit did, I chose to make the operation configurable with a 'BOOL' resource, since I wasn't sure what it was supposed to do. You should try to avoid using this field type in favor of the more explicit bit types above.
Decimal and hexadecimal integers, and resource IDs
DBYT, DWRD, DLNG, UBYT, UWRD, ULNG, HBYT, HWRD, HLNG, RSID
These instructions simply parse 8-bit, 16-bit, and 32-bit integer fields, depending on whether the field ends in 'BYT', 'WRD', or 'LNG'. The 'D' variants display and edit the data as signed decimal numbers; the 'U' variants as unsigned decimal; and the 'H' variants as unsigned hex. Parsing is subject to the current endian mode.
Since storing the resource ID of some related resource as a 2-byte integer is very common, Resorcerer has a special RSID field just for this purpose, allowing you to edit the related resource easily. The RSID field parses a 2-byte signed short integer, and displays it the same as a DWRD. But since the special field lets Resorcerer's Data Editor know that the number is really a resource ID, the editor can let you quickly interactively link to (i.e. open with an editor) the referenced resource in the same file. To do this, though, it needs to figure out what the resource type is, which it does in one of two ways. For instance, if your template field sequence contains the instructions
Listing 3: Resource References
Resource references in a template
RSID Resource ID of new icon suite ('icns')
TNAM Type of icon suite
RSID Resource ID of icon suite
then the Data Editor will be able to figure out which resource to open or create when you ask it to edit the referenced resource. With the first RSID field, the resource type to use is hardwired into the field's label ('icns'). In the second RSID field, because there is no single-quoted type, the editor searches back in the data for the first TNAM field and takes that data as the resource type.
Fixed-point numbers
FIXD, FRAC, SFRC, FWID, FXYZ
Fixed point numbers are integers whose bits are divided into two sets. The upper (more significant) set is the signed integral part of the number, and the lower (less significant) is the binary fractional part of the fixed-point number. The two parts of the number are separated by an implicit fixed decimal point. FIXD fields correspond the Mac Fixed data type [16.16]. FRAC fields are the same as the Mac Fract data type [2.30]. SFRC fields are the same as SmallFract data types [0.16] used in certain color models. FWID (Font Width) fields are used in various font-related resources [4.12]. Finally, FXYZ fields are used in some other color models [1.15]. All of these except the SFRC type display the data as signed fixed-point numbers.
Floating-point numbers
REAL, DOUB, EXTN, XT96, UNIV
Standard IEEE 32-b it and 64-bit single- and double-precision floating point numbers get parsed by the REAL and DOUB field types. EXTN parses the 68k 80-bit SANE floating point numbers; XT96 parses the 96-bit extended SANE floating point values, and UNIV parses the old THINK C Universal format 96-bit extended numbers. As far as PowerPC code is concerned, you only need to deal with REAL and DOUB floating point data.
Resorcerer's floating point support got added to the template system after developers working for a division of General Electric that sent balloons up into the atmosphere for data collection called saying they were keeping large amounts of floating point radar information in resources and needed to edit their data directly.
Creation and modification times
DATE, MDAT
These two fields parse a 32-bit unsigned long number, and display it as the time and date as the system interprets it (number of seconds since Jan 1, 1904). Any MDAT field (modification date) is automatically adjusted to be the current time and date when you close the resource's editing window after using the Data Editor to change any field in the resource.
Script, language, and region codes
SCPC, LNGC, RGNC
Each of these parses a signed 2-byte integer, the same as a DWRD. However, when the editor displays the value, it automatically provides a popup menu from which to choose all the standard values for the various codes, so that you don't have to remember or lookup which number stands for which language or region. These were implemented so that templates with these codes in them wouldn't also have to have copies of the long list of CASE fields (see below) that normally would be used to build the popups.
RGB colors and color tables
COLR, CLUT
The COLR field parses an RGB triplet: 3 2-byte integers, for the red, green, and blue color components, in that order. The Data Editor then displays the field in both numeric and graphic form, and you can change all three fields simultaneously using the system color picker.
The CLUT field parses an entire Color Lookup Table data structure into one hex block. It was implemented to make it easier to parse embedded cluts in 'PICT' data as single hex blocks.
QuickDraw Rectangles and Points
RECT, PNT (the point field type ends in a space)
The 'PNT' field parses a pair of signed short QuickDraw coordinate integers (vertical coordinate first), as in a Point data type. The RECT field does the same for Rect data types, and the Data Editor allows you to set the rectangle coordinates to new values graphically, using a marching ants marquee. If the marquee is in front of and graphically contained within the content area of any other Resorcerer window, the coordinates recorded are converted to local coordinates for that window; otherwise, the values placed into the RECT field are in global screen coordinates.
Alignment, pad, and filler bytes
AWRD (= AL02), ALNG (= AL04), AL08, AL16, FBYT, FWRD, FLNG, Fnmm)
Unlike C, Resorcerer (as well as Rez and ResEdit) templates perform no hidden data padding or alignment unless you explicitly declare it in the template. Given the myriad problems hidden alignment and padding causes developers, this is a good thing. However, it means that any data field, including numerical integers and floats, can occur at any byte offset, if you design the template that way (usually at some cost in efficiency, which on older 68000 systems can run to a 100% loss when the machine crashes).
The AWRD, ALNG, AL08, and AL16 fields allocate a variable number of 0 or more pad bytes so as to ensure that the following field will start on a byte boundary (as counted from the start of the resource data) divisible by the number 2, 4, 8, or 16 respectively.
The FBYT, FWRD, FLNG, and Fnmm fields declare filler bytes of various fixed lengths (1, 2, 4, and 0xnmm). Filler bytes are uneditable and generally set to 0. Filler bytes are typically used to declare space within the data structure that will be used by code at runtime (for instance, to cache a Handle), but which has no meaning within the context of editing the resource data "offline".
ASCII characters, 4-byte literal type codes, and pure ASCII text
CHAR, TNAM, TXTS, Tnmm
The CHAR field type parses and displays a single-character ASCII byte. The TNAM (Type Name) field parses a 4-character ASCII literal.
The TXTS field parses ASCII data until the end of the resource or until the end of an embedded, previously sized block of data within the resource. This field was implemented to fix the problem in the template distributed with ResEdit, which incorrectly declares a 'TEXT' resource as a null-byte terminated C string.
The Tnmm field declares a fixed-length buffer of 0xnmm bytes in which to expect 0 or more bytes of ASCII text. If the length of the text is less than 0xnmm bytes, the remaining bytes are set to zero-valued pad bytes.
Variable-length ASCII character strings
PSTR (=BSTR), WSTR, LSTR, ESTR, PPST, OSTR, CSTR, ECST, OCST
The PSTR field parses a Pascal-style string: a length byte followed by that many bytes of ASCII data. The WSTR field does the same, only the length is stored in a 2-byte integer; the LSTR field stores the length in a 4-byte long.
The ESTR field (even-length string) also parses or creates a Pascal string, but if the length of the data (including the length byte) is odd, it appends an extra zero pad byte. At first glance this might appear the same as the sequence [PSTR, AWRD], but there is a subtle difference: it's the same only if the PSTR begins on an even byte offset to begin with. The OSTR field is the opposite of the ESTR field. It adds the pad byte to make the total Pascal string storage always an odd number of bytes and should only be used for parsing certain ancient resources. Similarly, the PPST field was implemented as a hack in order to be able to describe certain MPW resources whose data was stored ESTR style, except that the extra pad byte was incorrectly included in the length byte (which makes it impossible to tell whether it's a pad byte or a data byte with value 0).
The CSTR, ECST, and OCST fields are equivalent to the PSTR, ESTR, and OSTR fields, only they operate on arbitrary-lengthh, null-byte-terminated C-style strings, as opposed to initial-length-byte Pascal-style strings.
Fixed-length ASCII character string buffers
Pnmm, Cnmm
These two fields each allocate a block of 0xnmm bytes and interpret the dat a in the block as either a Pascal or C string, followed by as many zero-valued pad bytes as are needed to fill the block.
The Pnmm field was designed to work in exactly the same way as all other Xnmm type fields: the field type declares a block of data of length 0xnmm bytes, and whatever gets stored in the block is interpreted according to X. Thus, the Cnmm, Fnmm, Hnmm, Pnmm, and Tnmm fields all indicate their sizes in exactly the same manner. I felt that consistency on this front was important. ResEdit interprets the Pnmm field differently, choosing to break consistency within the TMPL language in favor of making this one field conform to how such fields are declared in Pascal or C (sort of, since Str255's aren't declared as Str0FF's).
Resorcerer's Data Editor therefore supports both interpretations, on th e assumption that developers almost never allocate an odd number of bytes for the fixed-length buffer. The editor checks all Pnmm fields and if it finds one where 0xnmm is odd (as it is in ResEdit for an even-length buffer), it will give the user the opportunity to use ResEdit-style parsing.
Raw hex byte dumps
BHEX, WHEX, LHEX, BSHX, WSHX, LSHX, Hnmm, HEXS, HEXD
These fields parse and make editable blocks of bytes and present them as untyped hex data. With the first six types, the size of each block is taken from the initial byte, short, or long field. For instance, the BHEX field could be used to parse a Pascal string, only it would show the data in hex, not ASCII. The 'SHX' variants include the size of the size field in the count; the 'HEX' fields don't.
The HEXD field (Hex Dump), as originally defined in ResEdit, simply says take the rest of the resource data and edit it as untyped hex bytes. The HEXS field is similar, but is used for embedded hex dumps whose block sizes have been declared earlier.
68K disassembled code dumps
CODE
This is the same as HEXD, except that it asks the hex editing display to show the data as disassembled 68K code as well as in hex.
Automatically computed array repeat counts
BCNT, WCNT (= OCNT), LCNT, ZCNT, LZCT, FCNT
You can group a set of fields together and ask that the group be repeated until a condition is met. (In Rez, these are declared as arrays.) Usually this condition is given as an explicit repeat count in the data itself. The BCNT, WCNT, and LCNT fields each parse the count for the next repeated group of data fields, using an unsigned 1-, 2-, or 4-byte integer count (byte, word, or long, respectively). The WCNT type is the same as ResEdit's OCNT (one-based count) type. ZCNT (zero-based count) fields specify a 2-byte count that is 1 less than the number of repeated items that follow. The LZCT does the same, but stores the count (minus 1) in a 4-byte long.
One important difference from ResEdit in how Resorcerer implements these counts is that in ResEdit, the count field has to immediately precede the start of the group of array item; in Resorcerer, as in Rez, the count can occur much earlier in the data, with intervening fields between the initial count and the array of repeated items.
The FCNT (Fixed Count) field specifies a fixed-length count, where the count is not in the data at all, but taken from the field label string in the TMPL itself.
The BCNT field was originally implemented in order to build a TMPL for MPW Commando resources, as well as just plain generality. The FCNT field was first implemented for the benefit of parsing certain ColorSync resources.
Groupings of fields into repeated array items
LSTC, LSTZ, LSTS, LSTB, LSTE
These "field" types don't represent any data fields per se; rather, they declare the bounds of a subsequence of template instructions that represent a repeated item. You should use one of LSTC, LSTZ, LSTB, or LSTS to declare the start of the group, and use LSTE to end it.
LSTC starts a one-based counted list, whose count has previously been parsed from the data using any of the BCNT, WCNT, OCNT, or LCNT fields. The LSTZ field does the same thing, only the counted list is 0-based, meaning the count field contains one less than the number of items. I avoid 0-based lists whenever possible.
The LSTB initiator says the list simply repeats indefinitely, without the benefit of an initial count, until the end of the resource.
The LSTS initiator says the list repeats until the number of bytes parsed reaches a previously declared size field's value. This is called a Sized List as opposed to a Counted List.
All of these list structures can nest. You can have an indefinite list of counted lists of sized lists, etc.
Recursive templates for trees
SELF
You can describe simple recursive trees of similar items by using the SELF template field type as the sole field in a counted list group. A copy of the entire template's set of fields will be used in place of the SELF field when you edit the data. Because the recursive list must be counted, there is no infinite recursive descent: the leaf lists in the tree must all have an item count of 0.
Collections of common, required, key, or default values
CASE
Most data field types in a Resorcerer template can be followed by a set of 0 or more CASE instructions. These do not parse any actual data, but rather declare a set of named values that the previous field data can take on. They function similarly to an 'enum' or set of '#defines'. In addition, the first CASE declaration in the list of CASE fields lets you install a non-zero initialization value into the data whenever you are creating a new resource, or new repeated item.
As we will see in the next section, the set of CASE fields also declare the set of legal key values for a switched alternate record structure.
Integer keys for switch statements
KBYT, KWRD, KLNG, KUBT, KUWD, KULG, KHBT, KHWD, KHLG, KRID
In the more complex resource data formats, it often happens that a field's value determines one of several alternate formats later in the data. The determining field is called the key, and the set of alternates formats are called the switched items. In Rez, such structures are described using a switch statement, and each alternate item is called a case.
KBYT, KWRD, and KLNG each declare a signed 1-, 2-, or 4-byte integer whose value is to be used as a key to choose from amongst several alternate formats.
KUBT, KUWD, and KULG are basically the same, only the key values are treated as unsigned decimal integers.
KHBT, KHWD, and KHLG are again basically the same, only the Data Editor presents the key values as unsigned hex. These fields are particularly useful in Resorcerer's 'PICT' template, since the opcodes in a 'PICT' are all documented in Inside Mac using hex constants, not decimal, and each opcode is really a key value for alternate opcode data formats that follow.
The declaration of the set of legal key values follows the keyed field type as the usual set of CASE fields. This allows the Data Editor to collect the symbolic descriptions of the cases into a popup menu in the usual manner, making interactive editing of the key field very easy.
The KRID field is special: it does not declare a data field. Rather, it declares a pseudo-data field whose value is actually the current resource ID of the resource whose data the template is being used to edit. This makes it possible for the template to declare completely different data formats dependent solely on the resource ID (which is meta-information about the resource data) rather than on a previous key field in the data itself. The KRID field was originally implemented in order to deal with certain MPW and other older resources ('PREC's); however, its use is highly discouraged, since it obviates the reason for having a resource type attribute in the first place.
Character and literal type keys for switch statements
KCHR, KTYP
These declare 1- and 4-byte ASCII character literals whose values are to be keyed off of. The KCHR field is the keyed version of the CHAR field; the KTYP field is the keyed version of the TNAM field (which is the same as an OSType or ResType or FourCharCode).
Groupings of fields into alternate (keyed or switched) data structures
KEYB, KEYE
Following a key field and its set of n CASE declarations, there must be n alternate data formats, one for each CASE. The template field sequences describing each of these alternates must be delimited by a KEYB...KEYE pair. The label of the KEYB field must be the same as the character value of the earlier CASE declaration.
Automatically computed data sizes (in bytes)
BSIZ, WSIZ, LSIZ, BSKP, WSKP, LSKP, SKPE
Many data formats contain embedded sizes of sub-areas of the data. This is very important fo r desiging extensible formats, because they allow old code to traverse newer formats. The BSIZ, WSIZ, and LSIZ fields each store an unsigned byte count into a 8-, 16-, or 32-bit integer, respectively. The count stored is the number of bytes in the data, beginning at the first byte after the size field and continuing up to the next next matching SKPE field. The BSKP, WSKP, and LSKP fields are the same, except that the byte count they store includes their own sizes (e.g. an WSKP data field can never be less than 2 bytes).
The SKP variants were originally implemented first, in order to edit balloon help resources. This flavor of size field allows the format parsing code to skip an internal data block by simply by adding the value of the field at that offset into the data to the offset itself to get to the first byte offset after this sized block of data. Consequently, I called these fields "skip offsets", which is what the "SKP" part of the field types derives from. The SKPE (Skip End) field doesn't parse any data; it simply delineates where to stop counting bytes, and as such terminates the byte-counted area of the template for all 6 flavors of sizes.
Data insertion and deletion
+BYT, +WRD, +LNG, +nmm, +PST, +EST, +CST
-BYT, -WRD, -LNG, -nmm, -PST, -EST, -CST
Changing the structure that a template describes is as easy as inserting or deleting a field type using the Template Editor. Unfortunately, if you've already built resource data with the old template, the newly changed template won't be able to parse the old data correctly. This problem becomes particularly acute when the changed template fields are part of any repeated or keyed items. How do you change the old data at the same time as the old template?
The data insertion and deletion fields help you solve this problem. The insertion field types all begin with the '+' character; the deletion fields all begin with a '-'. An insertion field in a template tells the Data Editor to create the field when the old data is opened for editing, and to leave the data there when closing the resource. The deletion fields work exactly oppositely. It parses the old data field normally when you open the resource, but deletes the field when you close it. If an insertion or deletion field is part of a repeated item, it will do its work on every item in an existing array in a resource.
As usual, the 'BYT', 'WRD', and 'LNG' fields operate on 1-, 2-, and 4-byte fields; the 'nmm' field operates on 0xnmm bytes. The 'PST' fields insert or delete a Pascal string; the 'CST' fields do the same for C strings; and the 'EST' fields insert or delete even-Pascal strings (as declared by the 'ESTR' field).
Once you've changed the data to insert or delete fields, though, you have to remember to go back into the template and either remove any of the deletion fields, or change any of the insertion fields to the non-insertion equivalents, before opening the resource data again. You don't want to make the changes twice!
Template comments
DVDR
You can place a DVDR field anywhere in the TMPL field sequence. It doesn't parse any data, and its label string serves as a comment that the Data Editor draws for you on the right side of the data display.
Endian parsing mode
BNDN, LNDN, BIGE, LTLE
Templates default to parsing multi-byte numerical fields in big-endian style, since this is standard on the Mac. However, you can change the current endian interpretation to little-endian by using the LNDN or LTLE fields (which don't parse any data). The variants that end in "NDN" are invisible in the data editor display; the variants that end in "E" are shown.
Data pre- and post-processing with code plug-in filters
FLTR
Data formats can be arbitrarily complex, and it's quite easy to design a format that the simple parsing fields available in the TMPL "language" can't parse. Typically, any parts of the data format that depend algorithmically on other parts of the data can't be properly edited by a TMPL. Typical examples include encrypted or compressed data; fields whose values are arithmetic formulas dependent on other fields (e.g. rowBytes in a PixMap); checksums; etc.
If the first field in a TMPL is FLTR, the template is considered a filtered template. This means that a code resource of type 'FLTR' should accompany the template in the same file as the TMPL resource was found. The Data Editor will call the code resource to filter the data both on input to the TMPL parsing mechanism, and on output. Thus the FLTR code converts the initial Handle of data into an intermediate form that the TMPL describes. On output, the FLTR unconverts the data the TMPL describes, creating a Handle of data in its final form.
Summary
Resorcerer's TMPL language, which is a superset of ResEdit's, lets you easily describe and then interactively edit many different resource formats. Quite often, there is a one-to-one mapping from a Rez template to a Resorcerer template. Resorcerer is shipped with a great many templates for common Mac system and application resources that Apple engineers have designed using Rez.
A concise listing of all these template field types can be found at http://www.mathemaesthetics.com/ResTemplates.html.
Doug McKenna is the root hierarchæologist of Mathemæsthetics, Inc and has been a programmer for over 25 years. In addition to maintaining and enhancing Resorcerer® for the much of the last decade, he has worked on taxonomic database and music notation software. In his minimally-existent spare time, he enjoys gluing words together, hacks piano compositions, and researches geometric fractal tiling designs. Doug's last article for MacTech was FEZ: Frame Evading ZoomRects, which explained his award-winning "pushing the envelope" hack at MacHack '94. You can reach Doug at resorcerer@mathemaesthetics.com.