Copyright (c) Hyperion Entertainment and contributors.

IFFParse Library

From AmigaOS Documentation Wiki
Revision as of 21:14, 15 October 2017 by Daniel Jedlicka (talk | contribs) (Fixed a typo, added two missing library interface pointers.)
Jump to navigation Jump to search

IFFParse Library

The iffparse.library was created to help simplify the job of parsing IFF files. Unlike other IFF libraries, iffparse.library is not form-specific. This means that the job of interpreting the structure and contents of the IFF file is up to you. Previous IFF file parsers were either simple but not general, or general but not simple. IFFParse tries to be both simple and general.

The Structure of IFF Files

Many people have a misconception that IFF means image files. This is not the case. IFF (short for Interchange File Format) is a method of portably storing structured information in machine-readable form. The actual information can be anything, but the manner in which it is stored is very specifically detailed. This specification is the IFF standard.

The IFF standard was originally designed in 1985 by Electronic Arts in conjunction with a committee of developers. The full standard along with file descriptions and sample code is available at IFF Standard.

The goal of the IFF standard is to allow customers to move their own data between independently developed software products. The types of data objects to exchange are open-ended and currently include plain and formatted text, raster and structured graphics, fonts, music, sound effects, musical instrument descriptions, and animation. IFF addresses these needs by defining a standard for self-identifying file structures and rules for accessing these files.

Chunks: The Building Blocks of IFF

IFF files contain various types and amounts of data grouped in data chunks, each starting with a four-letter ASCII identifier (the chunk ID) followed by a 32-bit length count (the chunk size). The identifier and length count make it possible for IFF readers to skip over chunks that they don’t understand or don’t care about, and extract only the information they need. It may be helpful to think of these chunks as the building blocks of an IFF file.

The Chunk - The Building Block of IFF

The ‘CKID’ (chunk ID) characters of a real chunk will be a four letter identifier such as ‘BODY’ or ‘CMAP’, specifying the type and format of data stored in the chunk. Most chunks contain a simple defined grouping of byte, word, and long variables similar to the contents of a C structure. Others such as sound sample and bitmap image body chunks, contain a stream of compressed data.

Chunk writing is fairly straightforward with the one caveat that all chunks must be word-aligned. Therefore an odd-length chunk must be followed by a pad byte of zero (which is not counted in the size of the chunk). When chunks are nested, the enclosing chunk must state the total size of its composite contents (including any pad bytes).

About Chunk Length
Every IFF data chunk begins with a 4-character identifier field followed by a 32-bit size field (these 8 bytes are sometimes referred to as the chunk header). The size field is a count of the rest of the data bytes in the chunk, not including any pad byte. Hence, the total space occupied by a chunk is given by its size field (rounded to the nearest even number) + 8 bytes for the chunk header

Composite Data Types

Standard IFF files generally contain a variable number of chunks within one of the standard IFF composite data types (which may be thought of as chunks which contain other chunks). The IFF specification defines three basic composite data types, ‘FORM’, ‘CAT ’, and ‘LIST’. A special composite data type, the ‘PROP’, is found only inside a LIST.

Usage of Composite IFF Types

FORM A grouping of chunks which describe one set of data
LIST A grouping of the same type of FORMs with shared properties in a PROP
PROP May appear in a LIST to define chunks shared by several FORMs
CAT A concatenation of related FORMs or LISTs

The special IFF composite data types, like simple chunks, start with a 4-character identifier (such as ‘FORM’), followed by a 32-bit length. But their data begins with a second 4-character identifier that tells you what type of data is in the composite. For example, a FORM ILBM contains chunks describing a bitmap image, and a FORM FTXT contains chunks describing formatted text.

It may help to think of each composite data type as a box containing chunks, the IFF building blocks. The following figure shows a diagram of a typical composite.

The FORM – The Most Common IFF File Type

The example FORM in the previous figure is outlined below showing the size of chunks within the FORM. Notice that the size of a composite includes its second 4-character identifier (shown below as ‘NAME’).

. FORM 72 NAME  (72 is the size of the FORM starting with the “N” of NAME)
. . CKID 22     (then 22 bytes of data, so chunk header + chunk size is 30)
. . CKID 10     (then 10 bytes of data, so chunk header + chunk size is 18)
. . CKID 12     (then 12 bytes of data, so chunk header + chunk size is 20)
Note
In the example above, indentation represents the nesting of the chunks within the FORM. Real FORMs and chunks would have different four-character identifiers rather than ‘NAME’ and ‘CKID’.

Any odd-length chunks must have a pad byte of zero at the end of the chunk. As shown below, this pad byte is not counted in the size of the chunk but is counted in the size of any composite types (such as FORM) that contain an odd-length chunk. Warning: some IFF readers and writers do not deal with this properly.

. FORM 72 NAME  (72 is the size of the FORM starting with the “N” of NAME)
. . CKID 21     (then 21 bytes of data, so chunk header + chunk size is 29)
. . 0           (pad byte of zero, size 1)
. . CKID 10     (then 10 bytes of data, so chunk header + chunk size is 18)
. . CKID 12     (then 12 bytes of data, so chunk header + chunk size is 20)

Most IFF files are of the composite type FORM. Generally, a FORM is a simple grouping of chunks that provide information about some data, and the data itself. Although some standard chunks have common usage within different FORMS, the identity and format of most chunks within a FORM are relative to the definition and specification of the FORM. For example, a CRNG chunk in a FORM ILBM may have a totally different format and contents than a chunk named CRNG in a different type of FORM.

One of the most popular IFF FORMs that has been defined is the ILBM standard. This is how most Amiga bitmap imagery is stored. Since this is the most common IFF file, it is used frequently as an example.

Here is the output of a program called “Sift.c” that displays a text description of the contents of an IFF file (Sift.c is listed at the end of this article). The file shown below is a fairly simple ILBM.

. FORM 11068 ILBM
. . BMHD 20
. . CAMG 4
. . CMAP 12
. . BODY 10995
Computing the Size of a FORM
The size of the FORM (11,068 bytes) is equal to the sum of the sizes stated in each chunk contained within the FORM, plus 8 bytes for the overhead of each chunk header (4 bytes for the 4-character chunk ID, and 4 bytes for the 32-bit chunk size), plus 4 bytes for the FORM’s own 4-character ID (‘ILBM’), plus 1 byte more for each pad byte that follow any odd-length chunks.

Parsing an IFF File

Chunk reading requires a parser to scan each chunk and dispatch the proper access/conversion procedure. For a simple IFF file, such parsing may be relatively easy, consisting mainly reading in the data of desired chunks, and seeking over unwanted chunks (and the pad byte after odd-length chunks). Interpreting nested chunks is more complex, and requires a method for keeping track of the current context, i.e., the data which is still relevant at any particular depth into the nest. The original IFF specifications compare an IFF file to a C program. Such a metaphor can be useful in understanding the scope of of the chunks in an IFF file.

The IFFParse library addresses general IFF parsing requirements by providing a run-time library which can extract the chunks you want from an IFF file, with the ability to pass control to you when it reaches a chunk that requires special processing such as decompression. IFFParse also understands complex nested IFF formats and will keep track of the context for you.

Basic Functions and Structures of IFFParse Library

The structures and flags of the IFFParse library are defined in the include files <libraries/iffparse.h> and <libraries/iffparse.i>. IFF files are manipulated through a structure called an IFFHandle. Only some of the fields in the IFFHandle are publicly documented. The rest are managed internally by IFFParse. This handle is passed to all IFFParse functions, and contains the current parse state and position in the file. An IFFHandle is obtained by calling AllocIFF(), and freed through FreeIFF(). This is the only legal way to obtain and dispose of an IFFHandle.

The public portion of if IFFHandle is defined as follows:

/*
 * Structure associated with an active IFF stream.
 * "iff_Stream" is a value used by the client's read/write/seek functions -
 * it will not be accessed by the library itself and can have any value
 * (could even be a pointer or a BPTR).
 */
struct IFFHandle {
        ULONG   iff_Stream;
        ULONG   iff_Flags;
        LONG    iff_Depth;      /*  Depth of context stack.  */
        /*  There are private fields hiding here.  */
};

Stream Management

A stream is a linear array of bytes that may be accessed sequentially or randomly. DOS files are streams. IFFParse uses Hook structures (defined in <utility/hooks.h>) to implement a general stream management facility. This allows the IFFParse library to read, write, and seek any type of file handle or device by using an application-provided hook function to open, read, write, seek and close the stream.

Built on top of this facility, IFFParse has two internal stream managers: one for unbuffered DOS files (AmigaDOS filehandles), and one for the Clipboard.

Initialization

As shown above, the IFFHandle structure contains the public field iff_Stream. This is a longword that must be initialized to contain something meaningful to the stream manager. IFFParse never looks at this field itself (at least not directly). Your iff_Stream may be an AmigaDOS filehandle, or an IFFParse ClipboardHandle, or a custom stream of any type if you provide your own custom stream handler (see the section on Custom Stream Handlers). You must initialize your IFFHandle structure’s iff_Stream to your stream, and then initialize the IFFHandle’s flags and stream hook.

Three sample initializations are shown here. In the case of the internal DOS stream manager, iff_Stream is an AmigaDOS filehandle as returned by Open():

iff->iff_Stream = (ULONG) IDOS->Open ("filename", MODE_OLDFILE);
if(iff->iff_Stream) IIFFParse->InitIFFasDOS (iff);  /* use internal DOS stream manager */

In the case of the internal Clipboard stream manager, iff_Stream is a pointer to an IFFParse ClipboardHandle structure (the OpenClipboard() call used here is a function of iffparse.library, not the clipboard.device):

iff->iff_Stream = (ULONG) IIFFParse->OpenClipboard (PRIMARY_CLIP);
IIFFParse->InitIFFasClip (iff); /* use internal Clipboard stream manager  */

When using a custom handle such as an fopen() file handle, or a device other than the clipboard, you must provide your own flags and stream handler:

iff->iff_Stream = (ULONG) OpenMyCustomStream("foo");
IIFFParse->InitIFF (iff, IFFF_FSEEK | IFFF_RSEEK, &mystreamhook);

IFFParse “knows” the seek capabilities of DOS and ClipboardHandle streams, so InitIFFasDOS() and InitIFFasClip() set the flags for you.

You May Change the Seek Bits in iff_Flags
IFFParse sets IFFF_FSEEK

When using InitIFF() to provide a custom handler, you must also provide flags to tell IFFParse the capabilities of your stream. The flags are:

IFFF_FSEEK
This stream has forward-seek capability only.
IFFF_RSEEK
This stream has random-seek capability. IFFF_RSEEK tends to imply IFFF_FSEEK, but it’s best to specify both.

If neither flag is specified, you’re telling IFFParse that it can’t seek through the stream.

After your stream is initialized, call OpenIFF():

error = IIFFParse->OpenIFF (iff, IFFF_READ);

Once you establish a read/write mode (by passing IFFF_READ or IFFF_WRITE), you remain in that mode until you call CloseIFF().

Termination

Termination is simple. Just call CloseIFF(iff). This may be called at any time, and terminates IFFParse’s transaction with the stream. The stream itself is not closed. The IFFHandle may be reused; you may safely call OpenIFF() on it again. You are responsible for closing the streams you opened. A stream used in an IFFHandle must generally remain open until you CloseIFF().

Custom Streams

A custom stream handler can allow you (and iffparse.library) to use your compiler’s own file I/O functions such as fopen(), fread() and fwrite(), rather than the lower-level AmigaDOS equivalents Open(), Read(), and Write(). A custom stream handler could also be used to read or write IFF files from an Exec device or an unusual handler or file system.

If you install your own stream handler function, iffparse.library will call your function whenever it needs to read, write, or seek on your file. Your stream handler function will then perform these stream actions for iffparse.library. See the “Custom Stream Handlers” section for more information on custom stream handlers.

Parsing and Writing IFF

For information on parsing IFF see Parsing IFF.

For information on writing IFF see Writing IFF.

Context Functions

Internally, IFFParse maintains IFF nesting and scoping context via a context stack. The PushChunk() and PopChunk() functions get their names from this basic idea of the iffparse.library. Direct access to this stack is not allowed. However, many functions are provided to assist in examining and manipulating the context stack.

About the Context Stack
It is probably easier to think of a stack of blocks on a table in front of you when reading this discussion.

As the nesting level increases (as would happen when parsing a nested LIST or FORM), the depth of the context stack increases; new elements are added to the top. When these contexts expire, the ContextNodes are deleted and the stack shrinks.

Context Nodes

The current context is said to be the top element on the stack. Contextual information is stored in a structure called a ContextNode:

struct ContextNode {
        struct MinNode  cn_Node;
        LONG            cn_ID;
        LONG            cn_Type;
        LONG            cn_Size;        /*  Size of this chunk             */
        LONG            cn_Scan;        /*  # of bytes read/written so far */
        /*  There are private fields hiding here.  */
        };

CurrentChunk()

You can obtain a pointer to the current ContextNode through the function CurrentChunk():

currentnode = IIFFParse->CurrentChunk (iff);

The ContextNode tells you the type, ID, and size of the currently active chunk. If there is no currently active context, NULL is returned.

ParentChunk()

To find the parent of a context, you call ParentChunk() on the relevant ContextNode:

parentnode = IIFFParse->ParentChunk(currentnode);

If there is no parent context, NULL is returned.

The Default Context

When you first obtain an IFFHandle through AllocIFF(), a hidden default context node is created. You cannot get direct access to this node through CurrentChunk() or ParentChunk(). However, using StoreLocalItem(), you can store information in this context.

Context-Specific Data: LocalContextItems

ContextNodes can contain application data specific to that context. These data objects are called LocalContextItems. LocalContextItems (LCIs) are a grey-box structure which contain a type, ID and identification field. LCIs are used to store context-sensitive data. The format of this data is application-defined. A ContextNode can contain as many LCIs as you want.

IFFParse Context Stack

The previous figure shows the relationship between the IFFHandle, the ContextNodes, and the LCIs. The IFFHandle is the root of the tree, so to speak. ContextNodes “grow” out of the IFFHandle. In turn, LCIs "grow" out of the ContextNodes. What grows out of an LCI is client-defined.

AllocLocalItem()

To create an LCI, you use the function AllocLocalItem():

lci = IIFFParse->AllocLocalItem (type, id, ident, datasize);

If successful, you will be returned a pointer to an LCI having the specified type, ID, and identification values; and with datasize bytes of buffer space for your application to use.

LocalItemData()

To get a pointer to an LCIs data buffer, you use LocalItemData():

buf = IIFFParse->LocalItemData (lci);

You may read and write the buffer to your heart’s content; it is yours. You should not, however, write beyond the end of the buffer. The size of the buffer is what you asked for when you called AllocLocalItem().

Storing LCIs

Once you’ve created and initialized an LCI, you’ll want to attach it to a ContextNode. Though a ContextNode can have many LCIs, a given LCI can be linked to only one ContextNode. Once linked, an LCI cannot be removed from a ContextNode (this may change in the future). Storing an LCI in a ContextNode is done with the functions StoreLocalItem() and StoreItemInContext().

StoreLocalItem()

The StoreLocalItem() function is called as follows:

error = StoreLocalItem (iff, lci, position);

The position argument determines where the LCI is stored. The possible values are IFFSLI_ROOT, IFFSLI_TOP, and IFFSLI_PROP.

IFFSLI_ROOT
causes StoreLocalItem() to store your LCI in the default ContextNode.
IFFSLI_TOP
gets your LCI stored in the top (current) ContextNode.
The LCI Ends When the Current Context Ends
When the current context expires, your LCI will be deleted by the parser.

IFFSLI_PROP causes your LCI to be stored in the topmost context from which a property would apply. This is usually the topmost FORM or LIST chunk. For example, suppose you had a deeply nested ILBM FORM, and you wanted to store the BMHD property in its correct context such that, when the current FORM context expired, the BMHD property would be deleted. IFFSLI_PROP will cause StoreLocalItem() to locate the proper context for such scoping, and store the LCI there. See the section on "Finding the Prop Context" for additional information on the scope of properties.

StoreItemInContext()

StoreItemInContext() is used when you already have a pointer to the ContextNode to which you want to attach your LCI. It is called like so:

IIFFParse->StoreItemInContext (iff, lci, contextnode);

StoreItemInContext() links your LCI into the specified ContextNode. Then it searches the ContextNode to see if there is another LCI with the same type, ID, and identification values. If so, the old one is deleted.

FindLocalItem()

After you’ve stored your LCI in a ContextNode, you will no doubt want to be able to find it again later. You do this with the function FindLocalItem(), which is called as follows:

lci = IIFFParse->FindLocalItem (iff, type, id, ident);

FindLocalItem() attempts to locate an LCI having the specified type, ID, and identification values. The search proceeds as follows (refer to the previous figure to understand this better).

FindLocalItem() starts at the top (current) ContextNode and searches all LCIs in that context. If no matching LCIs are found, it proceeds down the context stack to the next ContextNode and searches all its LCIs. The process repeats until it finds the desired LCI (whereupon it returns a pointer to it), or reaches the end without finding anything (where it returns NULL).

Context Stack Position
LCIs higher in the stack will "override" lower LCIs with the same type, ID, and identification field. This is how property scoping is handled. As ContextNodes are popped off the context stack, all its LCIs are deleted as well. See the section on “Freeing LCIs” below for additional information on deleting LCIs.

Some Interesting Internal Details

Warning
This section details some internal implementation details of iffparse.library which may help you to understand it better. Use of the following information to do “clever” things in an application is forbidden and unsupportable. Don’t even think about it.

It turns out that StoredProperties, CollectionItems, and entry and exit handlers are all implemented using LCIs. For example, when you call FindProp(), you are actually calling a front-end to FindLocalItem(). The mysterious identification value (which has heretofore never been discussed) is a value which permits you to differentiate between LCIs having the same type and ID.

For instance, suppose you called PropChunk(), asking it to store an ILBM BMHD. PropChunk() will install an entry handler in the form of an LCI, having type equal to ‘ILBM’, ID equal to ‘BMHD’, and an identification value of IFFLCI_ENTRYHANDLER.

When an ILBM BMHD is encountered, the entry handler is called, and it creates and stores another LCI having type equal to ‘ILBM’, ID equal to ‘BMHD’ and an identification value of IFFLCI_PROP.

Thus, when you call FindProp(), it merely calls FindLocalItem() with your type and ID, and supplies IFFLCI_PROP for the identification value.

Therefore, handlers, StoredProperties, CollectionItems and your own custom LCIs can never be confused with each other, since they all have unique identification values. Since they are all handled (and searched for) in the same way, they all “override” each other in a consistent way. Just as StoredProperties higher in the context stack will be found and returned before identical ones in lower contexts, so will chunk handlers be found and invoked before ones lower on the context stack (recall FindLocalItem()’s search procedure).

This means you can temporarily override a chunk handler by installing an identical handler in a higher context. The handler will persist until the context in which it is stored expires, after which, the original one regains scope.

Error Handling

If at any time during reading or writing you encounter an error, the IFFHandle is left in an undefined state. Upon detection of an error, you should perform an abort sequence and CloseIFF() the IFFHandle. Once CloseIFF() has been called, the IFFHandle is restored to normalcy and may be reused.

Advanced Topics

This section discusses customizing of IFFParse data handling. For applications with special needs, IFFParse supports both custom stream handlers and custom chunk handlers.

Custom Stream Handlers

As discussed earlier, IFFParse contains built-in stream handlers for AmigaDOS file handles as returned by Open(), and for the clipboard.device. If you are using AmigaDOS filehandles or the clipboard.device, you need not supply a custom stream handler.

If you wish to use your compiler’s own file I/O functions (such as fread() ) or need to read or write to an unusual handler or Exec device, you must provide a custom stream handler for your IFFHandle. Your custom stream handler will be called to perform all reads, writes, and seeks on your custom stream. The allows you to use compiler file I/O functions, Exec device commands, or any other method to perform the requested stream operations.

If you are implementing your own custom stream handler, you will need to know the mechanics of hook call-backs, and how to interpret the parameters. An IFFParse custom stream handler is simply a function in your code that follows hook function conventions (Hook functions are also known as callback functions. See Utility Library for more details).

Installing a Custom Stream Handler

To initialize your IFFHandle to point to your custom stream and stream handler, you must open your stream and place a pointer to the stream in your IFFHandle’s iff_Stream field. This pointer could be the return from fopen(), or an Exec device I/O request, or any other type of pointer which will provide your custom stream handler with a way to manage your stream. For example:

iff->iff_Stream = (ULONG) fopen("foo");

Next, you must install your custom handler for this stream, and also tell IFFParse the seek capabilities of your stream. To install your custom stream handler, you must first set up a Hook structure (<utility/hooks.h>) to point to your stream handling function. Then use InitIFF() to install your stream handler and also tell IFFParse the seek capabilities of your stream. There is “some assembly required”.

For an IFFParse custom stream hook the function prototype looks like this:

int32 mystreamhandler(struct Hook *hook, struct IFFHandle *iff, struct IFFStreamCmd *actionpkt);

A return of 0 indicates success. A non-zero return indicates an error.

For example,

static int32 mystreamhandler
(
    struct Hook         *hook,
    struct IFFHandle    *iff,
    struct IFFStreamCmd *actionpkt
)
{
/*
 * Code to handle the stream commands - see end this section
 * for a complete example custom stream handler function.
 *
 */
}
 
 
/* Initialization of Hook structure for registerized handler function */
struct Hook mystreamhook
{
  { NULL },
  (HOOKFUNC) mystreamhandler,  /* h_Entry, registerized function entry */
  NULL,
  NULL
};
 
 
/* Open custom stream and InitIFF to custom stream handler */
if ( iff->iff_Stream = (ULONG) myopen("foo") )
        {
        IIFFParse->InitIFF (iff, IFFF_FSEEK | IFFF_RSEEK, &mystreamhook);
        }

Inside a Custom Stream Handler

When the library calls your stream handler, you’ll be passed a pointer to the Hook structure (hook in the example used here), a pointer to the IFFHandle (iff), and a pointer to an IFFStreamCmd structure (actionpkt). Your stream pointer may be found in iff->iff_Stream where you previously stored it. The IFFStreamCmd (actionpkt) will describe the action that IFFParse needs you to perform on your stream:

/* Custom stream handler is passed struct IFFStreamCmd *actionpkt */
struct IFFStreamCmd {
        LONG    sc_Command;     /*  Operation to be performed (IFFCMD_) */
        APTR    sc_Buf;         /*  Pointer to data buffer              */
        LONG    sc_NBytes;      /*  Number of bytes to be affected      */
        };
 
/* Possible call-back command values.  (Using 0 as the value for IFFCMD_INIT
 * was, in retrospect, probably a bad idea.)
 */
#define IFFCMD_INIT     0       /*  Prepare the stream for a session    */
#define IFFCMD_CLEANUP  1       /*  Terminate stream session            */
#define IFFCMD_READ     2       /*  Read bytes from stream              */
#define IFFCMD_WRITE    3       /*  Write bytes to stream               */
#define IFFCMD_SEEK     4       /*  Seek on stream                      */
#define IFFCMD_ENTRY    5       /*  You just entered a new context      */
#define IFFCMD_EXIT     6       /*  You're about to leave a context     */
#define IFFCMD_PURGELCI 7       /*  Purge a LocalContextItem            */

Your custom stream handler should perform the requested action on your custom stream, and then return 0 for success or an IFFParse error if an error occurred. The following code demonstrates a sample stream handler for a stream which was opened with a compiler’s fopen() buffered file I/O function:

static int32 mystreamhandler ( struct Hook *hook,
                              struct IFFHandle *iff,
                              struct IFFStreamCmd *actionpkt )
{
        FILE   *stream;
        int32  nbytes, error;
        uint8  *buf;
 
        stream  = (FILE *) iff->iff_Stream;     /* get your stream pointer */
        nbytes  = actionpkt->sc_NBytes;         /* length for command */
        buf     = (uint8 *) actionpkt->sc_Buf;  /* buffer for the command */
 
        /* Now perform the action specified by the actionpkt-&gt;sc_Command */
 
        switch (actionpkt->sc_Command) {
        case IFFCMD_READ:
                /*
                 * IFFCMD_READ means read sc_NBytes from the stream and place
                 * it in the memory pointed to by sc_Buf.  Be aware that
                 * sc_NBytes may be larger than can be contained in an int.
                 * This is important if you plan on recompiling this for
                 * 16-bit ints, since fread() takes int arguments.
                 *
                 * Any error code returned will be remapped by IFFParse into
                 * IFFERR_READ.
                 */
                error = (fread (buf, 1, nbytes, stream) != nbytes);
                break;
 
        case IFFCMD_WRITE:
                /*
                 * IFFCMD_WRITE is analogous to IFFCMD_READ.
                 *
                 * Any error code returned will be remapped by IFFParse into
                 * IFFERR_WRITE.
                 */
                error = (fwrite (buf, 1, nbytes, stream) != nbytes);
                break;
 
        case IFFCMD_SEEK:
                /*
                 * IFFCMD_SEEK asks that you performs a seek relative to the
                 * current position.  sc_NBytes is a signed number,
                 * indicating seek direction (positive for forward, negative
                 * for backward).  sc_Buf has no meaning here.
                 *
                 * Any error code returned will be remapped by IFFParse into
                 * IFFERR_SEEK.
                 */
                error = (fseek (stream, nbytes, 1) == -1);
                break;
 
        case IFFCMD_INIT:
                /*
                 * IFFCMD_INIT means to prepare your stream for reading.
                 * This is used for certain streams that can't be read
                 * immediately upon opening, and need further preparation.
                 * This operation is allowed to fail;  the error code placed
                 * in D0 will be returned directly to the client.
                 *
                 * An example of such a stream is the clipboard.  The
                 * clipboard.device requires you to set the io_ClipID and
                 * io_Offset to zero before starting a read.  You would
                 * perform such a sequence here.  (Stdio files need no such
                 * preparation, so we simply return zero for success.)
                 */
        case IFFCMD_CLEANUP:
                /*
                 * IFFCMD_CLEANUP means to terminate the transaction with
                 * the associated stream.  This is used for streams that
                 * can't simply be closed.  This operation is not allowed to
                 * fail;  any error returned will be ignored.
                 *
                 * An example of such a stream is (surprise!) the clipboard.
                 * It requires you to explicitly end reads by CMD_READing
                 * past the end of a clip, and end writes by sending a
                 * CMD_UPDATE.  You would perform such a sequence here.
                 * (Again, stdio needs no such sequence.)
                 */
                error = 0;
                break;
        }
        return (error);
}

Custom Chunk Handlers

Like custom stream handlers, custom chunk handlers are implemented using Hook structures. See the previous section for details on how a handler function may be interfaced using a Hook structure.

There are two types of chunk handlers: entry handlers and exit handlers. Entry handlers are invoked just after the parser enters the chunk; the stream will be positioned to read the first byte in the chunk. (If the chunk is a FORM, LIST, CAT, or PROP, the longword type will be read by the parser; the stream will be positioned to read the byte immediately following the type.) Exit handlers are invoked just before the parser leaves the chunk; the stream is not positioned predictably within the chunk.

Installing a Custom Chunk Handler

To install an entry handler, you call EntryHandler() in the following manner:

error = IIFFParse->EntryHandler (iff, type, id, position, hookptr, object);

An exit handler is installed by saying:

error = IIFFParse->ExitHandler (iff, type, id, position, hookptr, object);

In both cases, a handler is installed for chunks having the specified type and id. The position argument specifies in what context to install the handler, and is identical to the position argument used by StoreLocalItem(). The hookptr argument given above is a pointer to your Hook structure.

Inside a Custom Chunk Handler

The mechanics of receiving parameters through the Hook are covered in the “Custom Stream Handlers” section, and are not duplicated here. Refer to the EntryHandler() and ExitHandler() Autodocs for the contents of the registers upon entry.

Once inside your handler, you can call nearly all functions in the iffparse.library, however, you should avoid calling ParseIFF() from within chunk handlers.

Your handler runs in the same environment as whoever called ParseIFF(). The propagation sequence is:

mainline --calls--> ParseIFF() --calls--> your_handler()

Thus, your handler runs on your mainline’s stack, and can call any OS functions the mainline code can. (Your handler will have to set the global base pointer if your code uses base-relative addressing.)

The return code will affect the parser in a number of ways:

  • If you return zero (a normal, uneventful return), ParseIFF() will continue normally. If you return the value IFFERR_RETURN2CLIENT, ParseIFF() will stop and return the value zero to the mainline code.
  • If you return any other value, ParseIFF() will stop and return that value to the mainline code. This is how you should return error conditions to the client code.

The Object Parameter

The object parameter supplied to EntryHandler() and ExitHandler() is a pointer which will be passed to your handler when invoked. This pointer can be anything; the parser doesn’t use it directly. As an example, you might pass the pointer to the IFFHandle. This way, when your handler is called, you’ll be able to easily perform operations on the IFFHandle within your handler. Such code might appear as follows:

error = EntryHandler (iff, ID_ILBM, ID_BMHD, IFFSLI_ROOT, hook, iff);

And your handler would be declared as follows:

int32 MyHandler (struct Hook      *hook,
                 int32            *cmd,
                 struct IFFHandle *iff)

From within your handler, you could then call CurrentChunk(), ReadChunkBytes(), or nearly any other operation on the IFFHandle. Please refer to the EntryHandler() and ExitHandler() Autodocs for additional information on the use of chunk handlers.

Finding the Prop Context

Earlier it was mentioned that supplying a position value of IFFSLI_PROP to StoreLocalItem() would store it in the topmost property scope. FindPropContext() is the routine that finds that topmost context.

Property chunks (such as the BMHD, CMAP, and others) have dominion over the FORM that contains them; they are said to be “in scope” and their definition persists until the FORM’s context ends. Thus, a property chunk has a scoping level equal to the FORM that contains it; when the FORM ends, the property dies with it.

Consider a more complicated example. Suppose you have a LIST with a PROP in it. PROPs are the global variables of LISTs; thus a property chunk declared in a PROP will persist until the LIST’s context ends.

This is what FindPropContext() is looking for; a context level in which a property chunk may be installed.

FindPropContext() starts at the parent of the current context (second from the top of the context stack) and starts searching downward, looking for the first FORM or LIST context it finds. If it finds one, it returns a pointer to that ContextNode. If it can’t find a suitable context level, it returns NULL.

FindPropContext() is called as follows:

struct ContextNode *cn = IFFParse->FindPropContext (iff);

Freeing LCIs

Ordinarily, the parser will automatically delete LCIs you have allocated and installed. However, you may have a case where simply FreeVec()ing your LCI is not enough; you may need to free some ancillary memory, or decrement a counter, or send a signal, or something. This is where SetLocalItemPurge() comes in. It is called as follows:

IFFParse->SetLocalItemPurge (lci, hookptr);

When the parser is ready to delete your LCI, your purge handler code will be called through the Hook you supplied. You can then perform all your necessary operations. One of these operations should be to free the LCI itself. This is done with FreeLocalItem():

IIFFParse->FreeLocalItem (lci);

This deallocates the memory used to store the LCI and the client buffer allocated with it. FreeLocalItem() is only called as part of a custom purge handler.

As with custom chunk handlers, your purge handler executes in the same environment as the mainline code that called ParseIFF(). It is recommended that you keep purge handlers short and to the point; super clever stuff should be reserved for custom chunk handlers, or for the client’s mainline code. Custom purge handlers must always work; failures will be ignored.

IFF FORM Specifications

The specifications for Amiga IFF formats are maintained by the AmigaOS Development Team. The latest specifications are published in the IFF Standard. Updates of the IFF Manual, selected new FORMs and changes to existing FORMs are documented on this wiki.

Some of the most commonly used IFF FORMs are the four that were originally specified in the EA IFF-85 standard:

ILBM Bitmap images and palettes
FTXT Simple formatted text
SMUS Simple musical scores
8SVX 8-bit sound samples

Of these four, ILBM is the most commonly encountered FORM, and FTXT is becoming increasingly important since the conclip command passes clipped console text through the clipboard as FTXT. All data clipped to the clipboard must be in an IFF format.

This section will provide a brief summary of the ILBM and FTXT FORMs and their most used common chunks. Please consult the EA-IFF specifications for additional information.

FORM ILBM

The IFF file format for graphic images on the Amiga is called FORM ILBM (InterLeaved BitMap). It follows a standard parsable IFF format.

ILBM.BMHD BitMapHeader Chunk

The most important chunk in a FORM ILBM is the BMHD (BitMapHeader) chunk which describes the size and compression of the image:

typedef UBYTE Masking;  /* Choice of masking technique - Usually 0. */
#define mskNone                 0
#define mskHasMask              1
#define mskHasTransparentColor  2
#define mskLasso                3
 
/* Compression algorithm applied to the rows of all source
 * and mask planes. &quot;cmpByteRun1&quot; is byte run encoding.
 * Do not compress across rows!  Compression is usually 1.
 */
typedef UBYTE Compression;
#define cmpNone                 0
#define cmpByteRun1             1
 
/* The BitMapHeader structure expressed as a C structure */
typedef struct {
        UWORD w, h;             /* raster width &amp; height in pixels      */
        WORD  x, y;             /* pixel position for this image        */
        UBYTE nPlanes;          /* # bitplanes (without mask, if any)   */
        Masking     masking;    /* One of the values above.  Usually 0  */
        Compression compression;/* One of the values above.  Usually 1  */
        UBYTE reserved1;        /* reserved; ignore on read, write as 0 */
        UWORD transparentColor; /* transparent color number. Usually 0  */
        UBYTE xAspect, yAspect; /* pixel aspect, a ratio width : height */
        WORD  pageWidth, pageHeight;    /* source &quot;page&quot; size in pixels */
} BitMapHeader;
Warning
This hex dump is shown only to help the reader understand how IFF chunks appear in a file. You cannot ever depend on any particular ILBM chunk being at any particular offset into the file. IFF files are composed, in their simplest form, of chunks within a FORM. Each chunk starts with a 4-letter chunkID, followed by a 32-bit length of the rest of the chunk. You must parse IFF files, skipping past unneeded or unknown chunks by seeking their length (+1 if odd length) to the next 4-letter chunk ID. In a real ILBM file, you are likely to encounter additional optional chunks. See the IFF Specification listed in IFF Standard for additional information on such chunks.
0000: 464F524D 00016418 494C424D 424D4844    FORM..d.ILBMBMHD
0010: 00000014 01400190 00000000 06000100    .....@..........
0020: 00000A0B 01400190 43414D47 00000004    .....@..CAMG....
0030: 00000804 434D4150 00000030 001000E0    ....CMAP...0....
0040: E0E00000 20000050 30303050 50500030    .... ..P000PPP.0
0050: 90805040 70707010 60E02060 E06080D0    ..P@ppp.`. `.`..
0060: A0A0A0A0 90E0C0C0 C0D0A0E0 424F4459    ............BODY
0070: 000163AC F8000F80 148A5544 2ABDEFFF    ..c.......UD*...  etc.

Interpretation:

      'F O R M' length  'I L B M''B M H D'<-start of BitMapHeader chunk
0000: 464F524D 00016418 494C424D 424D4844    FORM..d.ILBMBMHD

       length  WideHigh XorgYorg PlMkCoRe <- Planes Mask Compression Reserved
0010: 00000014 01400190 00000000 06000100    .....@..........

      TranAspt PagwPagh 'C A M G' length  <- start of C-AMiGa View modes chunk
0020: 00000A0B 01400190 43414D47 00000004    .....@..CAMG....

dir include:
      ViewMode 'C M A P' length   R g b R <- ViewMode 800=HAM | 4=LACE
0030: 00000804 434D4150 00000030 001000E0    ....CMAP...0....

       g b R g  b R g b  R g b R  g b R g <- Rgb's are for reg0 thru regN
0040: E0E00000 20000050 30303050 50500030    .... ..P000PPP.0

       b R g b  R g b R  g b R g  b R g b
0050: 90805040 70707010 60E02060 E06080D0    ..P@ppp.`. `.`..

       R g b R  g b R g  b R g b 'B O D Y'
0060: A0A0A0A0 90E0C0C0 C0D0A000 424F4459    ............BODY

       length   start of body data        <- Compacted (Compression=1 above)
0070: 000163AC F8000F80 148A5544 2ABDEFFF    ..c.......UD*...
0080: FFBFF800 0F7FF7FC FF04F85A 77AD5DFE    ...........Zw.].  etc.

Simple CAMG ViewModes:  HIRES=0x8000  LACE=0x4  HAM=0x800  HALFBRITE=0x80

( ILBMs may contain a LONGWORD ViewPort ModeID in CAMG )

Interpreting ILBMs

ILBM is a fairly simple IFF FORM. All you really need to deal with to extract the image are the following chunks:

Information about the size, depth, compaction method (see interpreted hex dump above).

Optional Amiga ViewModes chunk. Most HAM and HALFBRITE ILBMs should have this chunk. If no CAMG chunk is present, and image is 6 planes deep, assume HAM and you’ll probably be right. Some Amiga ViewModes flags are HIRES=0x8000, LACE=0x4, HAM=0x800, HALFBRITE=0x80. 2.0 ILBM writers should write a full 32-bit mode ID in the CAMG. See the IFF Manual for more information on writing and interpreting 32-bit CAMG values.

RGB values for color registers 0 to N. Previously, 4-bit RGB components each left justified in a byte. These should now be stored as a full 8-bit RGB values by duplicating 4-bit values in the high and low nibble of the byte.

The pixel data, stored in an interleaved fashion as follows (each line individually compacted if BMHD Compression = 1):

plane 0 scan line 0
plane 1 scan line 0
plane 2 scan line 0
...
plane n scan line 0
plane 0 scan line 1
plane 1 scan line 1
etc.

Also watch for AUTH Author chunks and (c) copyright chunks and preserve any copyright information if you rewrite the ILBM.

ILBM BODY Compression

The BODY contains pixel data for the image. Width, Height, and depth (Planes) is specified in the BMHD.

If the BMHD Compression byte is 0, then the scan line data is not compressed. If Compression=1, then each scan line is individually compressed as follows:

while (not produced the desired number of bytes)

    /* get a byte, call it N */

    if (N >= 0 && N <= 127)
        /* copy the next N+1 bytes literally */

    if (N >= -127 && N <= -1)
        /* repeat the next byte N+1 times */

    if (N == -128)
        /* skip it, presumably it's padding */

Interpreting the Scan Line Data

If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if necessary, you will have N planes of pixel data. Color register used for each pixel is specified by looking at each pixel thru the planes. For instance, if you have 5 planes, and the bit for a particular pixel is set in planes 0 and 3:

PLANE     4 3 2 1 0
PIXEL     0 1 0 0 1

then that pixel uses color register binary 01001 = 9.

The RGB value for each color register is stored in the CMAP chunk of the ILBM, starting with register 0, with each register’s RGB value stored as one byte of R, one byte G, and one byte of B, with each component left justified in the byte. (i.e. Amiga R, G, and B components are each stored in the high nibble of a byte)

But, if the picture is HAM or HALFBRITE, it is interpreted differently. Hopefully, if the picture is HAM or HALFBRITE, the package that saved it properly saved a CAMG chunk (look at a hex dump of your file with ASCII interpretation - you will see the chunks - they all start with a 4-ASCII-char chunk ID). If the picture is 6 planes deep and has no CAMG chunk, it is probably HAM. If you see a CAMG chunk, the ‘CAMG’ is followed by the 32-bit chunk length, and then the 32-bit Amiga view mode flags.

HAM pictures will have the 0x800 bit set in CAMG chunk ViewModes. HALFBRITE pictures will have the 0x80 bit set. See the graphics library articles for more information on HAM and HALFBRITE modes.

Other ILBM Notes

Amiga ILBMs images must be stored as an even number of bytes in width. However, the ILBM BMHD field w (width) should describe the actual image width, not the rounded up width as stored.

ILBMs created with Electronic Arts IBM or Amiga Deluxe Paint II packages are compatible (though you may have to use a ‘.lbm’ filename extension on an IBM). The ILBM graphic files may be transferred between the machines (or between the Amiga and IBM sides your Amiga if you have a CBM Bridgeboard card installed) and loaded into either package.

FORM FTXT

The FTXT (Formatted TeXT) form is the standard format for sharing text on the Amiga. The console device clip and paste functions, in conjunction with the startup-sequence conclip command, pass clipped console text through the clipboard as FTXT.

By supporting reading and writing of clipboard device FTXT, your application will allow users to cut and paste text between your application, other applications, and Amiga Shell windows.

The FTXT form is very simple. Generally, for clip and paste operations, you will only be concerned with the CHRS (character string) chunks of an FTXT. There may be one or more CHRS chunks, each of which contains a non-NULL-terminated string of ASCII characters whose length is equal to the length (StoredProperty.sp_Size) of the chunk.

Be aware that if you CollectionChunk() the CHRS chunks of an FTXT, the list accumulated by IFFParse will be in reverse order from the chunk order in the file. See the ClipFTXT.c example at the end of this article for a simple example of reading (and optionally writing) FTXT to and from the clipboard. See also the Clipboard Device and Console Device for more information on console clip and paste.

IFFParse Examples

Two examples follow: “ClipFTXT.c” and “Sift.c”. These are simple examples that demonstrate the use of the IFFParse library. More complex examples for displaying and saving ILBMs may be found in the IFF Standard section.

Clipboard FTXT Example

ClipFTXT.c demonstrates reading (and optional writing) of clipboard device FTXT. This example may be used as the basis for supporting console pastes.

/*
 *
 * clipftxt.c:   Writes ASCII text to clipboard unit as FTXT
 *               (All clipboard data must be IFF)
 *
 * Usage: clipftxt unitnumber
 *
 * To convert to an example of reading only, comment out #define WRITEREAD
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/iffparse.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/iffparse.h>
 
#include <stdlib.h>
#include <string.h>
 
/* Causes example to write FTXT first, then read it back
 * Comment out to create a reader only
 */
#define WRITEREAD
 
 
#define MINARGS 2
 
CONST_STRPTR usage = "Usage: clipftxt unitnumber (use zero for primary unit)";
 
/*
 * Text error messages for possible IFFERR_#? returns from various
 * IFF routines.  To get the index into this array, take your IFFERR code,
 * negate it, and subtract one.
 *  idx = -error - 1;
 */
char    *errormsgs[] = {
        "End of file (not an error).",
        "End of context (not an error).",
        "No lexical scope.",
        "Insufficient memory.",
        "Stream read error.",
        "Stream write error.",
        "Stream seek error.",
        "File is corrupt.",
        "IFF syntax error.",
        "Not an IFF file.",
        "Required call-back hook missing.",
        "Return to client.  You should never see this."
};
 
#define RBUFSZ 512
 
#define  ID_FTXT        MAKE_ID('F','T','X','T')
#define  ID_CHRS        MAKE_ID('C','H','R','S')
 
struct IFFParseIFace *IIFFParse;
 
UBYTE mytext[]="This FTXT written to clipboard by clipftxt example.\n";
 
int main(int argc, char **argv)
{
    struct IFFHandle    *iff = NULL;
    struct ContextNode  *cn;
    long                error=0, unitnumber=0, rlen;
    int textlen;
    UBYTE readbuf[RBUFSZ];
 
        /* if not enough args or '?', print usage */
        if(((argc)&&(argc<MINARGS))||(argv[argc-1][0]=='?'))
                {
                IDOS->Printf("%s\n", usage);
                return RETURN_WARN;
                }
 
        unitnumber = atoi(argv[1]);
 
        struct Library *IFFParseBase = IExec->OpenLibrary("iffparse.library", 50);
        IIFFParse = (struct IFFParseIFace*)IExec->GetInterface(IFFParseBase, "main", 1, NULL);
 
        if (IIFFParse == NULL)
                {
                IDOS->Printf("Can't open iff parsing library.\n");
                goto bye;
                }
 
        /*
         * Allocate IFF_File structure.
         */
        if (!(iff = IIFFParse->AllocIFF ()))
                {
                IDOS->Printf("AllocIFF() failed.\n");
                goto bye;
                }
 
        /*
         * Set up IFF_File for Clipboard I/O.
         */
        if (!(iff->iff_Stream = (ULONG) OpenClipboard (unitnumber)))
                {
                IDOS->Printf("Clipboard open failed.\n");
                goto bye;
                }
        else IDOS->Printf("Opened clipboard unit %ld\n", unitnumber);
 
        IIFFParse->InitIFFasClip (iff);
 
#ifdef WRITEREAD
 
        /*
         * Start the IFF transaction.
         */
        if (error = IIFFParse->OpenIFF (iff, IFFF_WRITE))
                {
                IDOS->Printf("OpenIFF for write failed.\n");
                goto bye;
                }
 
        /*
         * Write our text to the clipboard as CHRS chunk in FORM FTXT
         *
         * First, write the FORM ID (FTXT)
         */
        if(!(error = IIFFParse->PushChunk(iff, ID_FTXT, ID_FORM, IFFSIZE_UNKNOWN)))
                {
                /* Now the CHRS chunk ID followed by the chunk data
                 * We'll just write one CHRS chunk.
                 * You could write more chunks.
                 */
                if(!(error = IIFFParse->PushChunk(iff, 0, ID_CHRS, IFFSIZE_UNKNOWN)))
                        {
                        /* Now the actual data (the text) */
                        textlen = strlen(mytext);
                        if(IIFFParse->WriteChunkBytes(iff, mytext, textlen) != textlen)
                                {
                                IDOS->Printf("Error writing CHRS data.\n");
                                error = IFFERR_WRITE;
                                }
                        }
                if(!error) error = IIFFParse->PopChunk(iff);
                }
        if(!error) error = IIFFParse->PopChunk(iff);
 
 
        if(error)
                {
                IDOS->Printf("IFF write failed, error %ld: %s\n",
                        error, errormsgs[-error - 1]);
                goto bye;
                }
        else IDOS->Printf("Wrote text to clipboard as FTXT\n");
 
        /*
         * Now let's close it, then read it back
         * First close the write handle, then close the clipboard
         */
        IIFFParse->CloseIFF(iff);
        if (iff->iff_Stream) CloseClipboard ((struct ClipboardHandle *)
                                                iff->iff_Stream);
 
        if (!(iff->iff_Stream = (ULONG) OpenClipboard (unitnumber)))
                {
                IDOS->Printf("Reopen of Clipboard failed.\n");
                goto bye;
                }
        else IDOS->Printf("Reopened clipboard unit %ld\n", unitnumber);
 
#endif /* WRITEREAD */
 
        if (error = IIFFParse->OpenIFF (iff, IFFF_READ))
                {
                IDOS->Printf("OpenIFF for read failed.\n");
                goto bye;
                }
 
        /* Tell iffparse we want to stop on FTXT CHRS chunks */
        if (error = IFFParse->StopChunk(iff, ID_FTXT, ID_CHRS))
                {
                IDOS->Printf("StopChunk failed.\n");
                goto bye;
                }
 
        /* Find all of the FTXT CHRS chunks */
        while(1)
                {
                error = IIFFParse->ParseIFF(iff,IFFPARSE_SCAN);
                if(error == IFFERR_EOC) continue;       /* enter next context */
                else if(error) break;
 
                /* We only asked to stop at FTXT CHRS chunks
                 * If no error we've hit a stop chunk
                 * Read the CHRS chunk data
                 */
                cn = IIFFParse->CurrentChunk(iff);
 
                if((cn)&&(cn->cn_Type == ID_FTXT)&&(cn->cn_ID == ID_CHRS))
                        {
                        IDOS->Printf("CHRS chunk contains:\n");
                        while((rlen = IIFFParse->ReadChunkBytes(iff,readbuf,RBUFSZ)) > 0)
                                {
                                IDOS->Write(Output(),readbuf,rlen);
                                }
                        if(rlen < 0)    error = rlen;
                        }
                }
 
        if((error)&&(error != IFFERR_EOF))
                {
                IDOS->Printf ("IFF read failed, error %ld: %s\n",
                        error, errormsgs[-error - 1]);
                }
 
bye:
        if (iff) {
                /*
                 * Terminate the IFF transaction with the stream.  Free
                 * all associated structures.
                 */
                IIFFParse->CloseIFF (iff);
 
                /*
                 * Close the clipboard stream
                 */
                if (iff->iff_Stream)
                                CloseClipboard ((struct ClipboardHandle *)
                                                iff->iff_Stream);
                /*
                 * Free the IFF_File structure itself.
                 */
                IIFFParse->FreeIFF (iff);
                }
        IExec->DropInterface((struct Interface*)IIFFParse);
        IExec->CloseLibrary (IFFParseBase);
 
        return RETURN_OK;
}

IFF Scanner Example

"Sift.c" lists the type and size of every chunk in an IFF file and, and checks the IFF file for correct syntax. You should use "Sift" to check IFF files created by your programs.

/*
 *
 * sift.c:       Takes any IFF file and tells you what's in it.  Verifies syntax and all that cool stuff.
 *
 * Usage: sift -c          ; For clipboard scanning
 *    or  sift <file>      ; For DOS file scanning
 *
 * Reads the specified stream and prints an IFFCheck-like listing of the contents of the IFF file, if any.
 * Stream is a DOS file for <file> argument, or is the clipboard's primary clip for -c.
 * This program must be run from a CLI.
 *
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/iffparse.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/iffparse.h>
 
#include <stdlib.h>
#include <string.h>
 
#define MINARGS 2
 
CONST_STRPTR usage = "Usage: sift IFFfilename (or -c for clipboard)";
 
void PrintTopChunk(struct IFFHandle *);  /* prototype for our function */
 
/*
 * Text error messages for possible IFFERR_#? returns from various IFF routines.  To get the index into
 * this array, take your IFFERR code, negate it, and subtract one.
 *  idx = -error - 1;
 */
char    *errormsgs[] = {
        "End of file (not an error).", "End of context (not an error).", "No lexical scope.",
        "Insufficient memory.", "Stream read error.", "Stream write error.",
        "Stream seek error.", "File is corrupt.", "IFF syntax error.",
        "Not an IFF file.", "Required call-back hook missing.", "Return to client.  You should never see this."
};
 
struct IFFParseIFace *IIFFParse = NULL;
 
int main(int argc, char **argv)
{
    struct IFFHandle    *iff = NULL;
    int32                error;
    int16                cbio;
 
        /* if not enough args or '?', print usage */
        if(((argc)&&(argc<MINARGS))||(argv[argc-1][0]=='?'))
                {
                IDOS->Printf("%s\n", usage);
                goto bye;
                }
 
        /* Check to see if we are doing I/O to the Clipboard. */
        cbio = (argv[1][0] == '-'  &&  argv[1][1] == 'c');
 
        struct Library *IFFParseBase = IExec->OpenLibrary("iffparse.library", 50);
        IIFFParse = (struct IFFParseIFace*)IExec->GetInterface(IFFParseBase, "main", 1, NULL);
        if (IIFFParse == NULL)
                {
                IDOS->Printf("Can't open iff parsing library.\n");
                goto bye;
                }
 
        /* Allocate IFF_File structure. */
        if (!(iff = IIFFParse->AllocIFF()))
                {
                IDOS->Printf("AllocIFF() failed.\n");
                goto bye;
                }
 
        /*
         * Internal support is provided for both AmigaDOS files, and the clipboard.device. This bizarre
         * 'if' statement performs the appropriate machinations for each case.
         */
        if (cbio)
                {
                /*
                 * Set up IFF_File for Clipboard I/O.
                 */
                if (!(iff->iff_Stream = (ULONG) IIFFParse->OpenClipboard(PRIMARY_CLIP)))
                        {
                        IDOS->Printf("Clipboard open failed.\n");
                        goto bye;
                        }
                IIFFParse->InitIFFasClip(iff);
                }
        else
                {
                /* Set up IFF_File for AmigaDOS I/O.  */
                if (!(iff->iff_Stream = IDOS->Open(argv[1], MODE_OLDFILE)))
                        {
                        IDOS->Printf("File open failed.\n");
                        goto bye;
                        }
                IIFFParse->InitIFFasDOS(iff);
                }
 
        /* Start the IFF transaction. */
        if (error = IIFFParse->OpenIFF(iff, IFFF_READ))
                {
                IDOS->Printf("OpenIFF failed.\n");
                goto bye;
                }
 
        while (1)
                {
                /*
                 * The interesting bit. IFFPARSE_RAWSTEP permits us to have precision monitoring of the
                 * parsing process, which is necessary if we wish to print the structure of an IFF file.
                 * ParseIFF() with _RAWSTEP will return the following things for the following reasons:
                 *
                 * Return code:                 Reason:
                 * 0                            Entered new context.
                 * IFFERR_EOC                   About to leave a context.
                 * IFFERR_EOF                   Encountered end-of-file.
                 * <anything else>              A parsing error.
                 */
                error = IIFFParse->ParseIFF(iff, IFFPARSE_RAWSTEP);
 
                /*
                 * Since we're only interested in when we enter a context, we "discard" end-of-context
                 * (_EOC) events.
                 */
                if (error == IFFERR_EOC)
                        continue;
                else if (error)
                        /*
                         * Leave the loop if there is any other error.
                         */
                        break;
 
 
                /* If we get here, error was zero. Print out the current state of affairs. */
                PrintTopChunk(iff);
                }
 
        /*
         * If error was IFFERR_EOF, then the parser encountered the end of
         * the file without problems. Otherwise, we print a diagnostic.
         */
        if (error == IFFERR_EOF)
                IDOS->Printf("File scan complete.\n");
        else
                IDOS->Printf("File scan aborted, error %ld: %s\n",
                        error, errormsgs[-error - 1]);
 
bye:
        if (iff) {
                /* Terminate the IFF transaction with the stream. Free all associated structures. */
                IIFFParse->CloseIFF(iff);
 
                /*
                 * Close the stream itself.
                 */
                if (iff->iff_Stream)
                        if (cbio)
                                IIFFParse->CloseClipboard((struct ClipboardHandle *)iff->iff_Stream);
                        else
                                IDOS->Close(iff->iff_Stream);
 
                /* Free the IFF_File structure itself. */
                IIFFParse->FreeIFF(iff);
                }
        IExec->DropInterface((struct Interface*)IIFFParse);
        IExec->CloseLibrary(IFFParseBase);
 
        return RETURN_OK;
}
 
void PrintTopChunk(struct IFFHandle *iff)
{
        struct ContextNode      *top;
        int32                   i;
        char                    idbuf[5];
 
        /* Get a pointer to the context node describing the current context. */
        if (!(top = IIFFParse->CurrentChunk(iff)))
                return;
 
        /*
         * Print a series of dots equivalent to the current nesting depth of chunks processed so far.
         * This will cause nested chunks to be printed out indented.
         */
        for (i = iff->iff_Depth;  i--; )
                IDOS->Printf(". ");
 
        /* Print out the current chunk's ID and size. */
        IDOS->Printf("%s %ld ", IIFFParse->IDtoStr(top->cn_ID, idbuf), top->cn_Size);
 
        /* Print the current chunk's type, with a newline. */
        IDOS->Printf(IIFFParse->IDtoStr(top->cn_Type, idbuf));
}

Function Reference

The following are brief descriptions of the IFFParse functions discussed in this article. Further information about these and other IFFParse functions can be found in the SDK.

Function Description
AllocIFF() Creates an IFFHandle structure.
FreeIFF() Frees the IFFHandle structure created with AllocIFF().
OpenIFF() Initialize an IFFHandle structure to read or write an IFF stream.
CloseIFF() Closes an IFF context.
InitIFF() Initialize an IFFHandle as a user-defined stream.
InitIFFasDOS() Initialize an IFFHandle as an AmigaDOS stream.
InitIFFasClip() Initialize an IFFHandle as a clipboard stream.
OpenClipboard() Create a handle on a clipboard unit for InitIFFasClip().
ParseIFF() Parse an IFF file from an IFFHandle stream.
ReadChunkBytes() Read bytes from the current chunk into a buffer.
ReadChunkRecords() Read record elements from the current chunk into a buffer.
StopChunk() Declare a chunk that should cause ParseIFF() to return.
CurrentChunk() Get the context node for the current chunk.
PropChunk() Specify a property chunk to store.
FindProp() Search for a stored property in a given context.
CollectionChunk() Declare a chunk type for collection.
FindCollection() Get a pointer to the current list of collection items.
StopOnExit() Declare a stop condition for exiting a chunk.
EntryHandler() Add an entry handler to the IFFHandle context.
ExitHandler() Add an exit handler to the IFFHandle context.
PushChunk() Push a given context node onto the top of the context stack.
PopChunk() Pop the top context node off of the context stack.
CurrentChunk() Get the top context node for the current chunk.
ParentChunk() Get the nesting context node for a given chunk.
AllocLocalItem() Create a LocalContextItem (LCI) structure.
LocalItemData() Returns a pointer to the user data of a LocalContextItem (LCI).
StoreLocalItem() Insert a LocalContextItem (LCI).
StoreItemInContext() Store a LocalContextItem in a given context node.
FindPropContext() Find the property context for the current state.
FindLocalItem() Return a LocalContextItem from the context stack.
FreeLocalItem() Free a LocalContextItem (LCI) created with AllocLocalItem().
SetLocalItemPurge() Set purge vector for a local context item.