Text data objects for Allegro 
By Chris La Mantia
Version 2.0 WIP4

--------------------------------------------------------------------------------

If you have a program that deals with a large number of strings, you have a few ways of getting those strings into a program. You can hardcode the strings into your code; this allows easy creation of ragged arrays, saving space at the expense of flexibility. You could put the strings in an external file, reading them in at runtime; this, however, brings in the ugly spectre of doing dynamic allocation to get ragged arrays, or reading a fixed maximum number of strings at fixed sizes, wasting memory and reducing flexibility. Here, I present another approach for users of the Allegro game programming library, by Shawn Hargreaves: loadable text data objects.

What is a text data object?
A text data object is a dynamically allocated string array. It can be stored in an Allegro datafile and loaded as needed by the library, or any text file can be loaded as a text data object. Allegro, with the help of the loader routines in textdata.c, will allocate the memory individually for each string, and will return a pointer to a table of string pointers; thus, a dynamically-allocated ragged array.

Installation
To use the library, simply add the textdata.c file to your project and include "textdata.h" in your source code. 

What functions are included here?
install_textdata();
This macro registers the text data type with Allegro; you must call it before loading any datafiles that include text data objects.

strcount(tdata);
This macro returns the number of strings in a text data object. Calling this macro on anything else will return funky results.

char **load_textdata(char *filename);
Creates and loads a text data object from a file.

void destroy_textdata(void *data);
This function de-allocated the memory used by a text data object. Again, you should not call this function directly; Allegro will call it when you unload your data file.

char **create_textdata(void);
Creates an empty text data object.

char **add_textdata_string(char ** tdo, char *string);
Adds a string to the end of a text data object.

char **delete_textdata_string(char ** tdo, int s);
Removes string s from the specified text data object.

char **replace_textdata_string(char ** tdo, int s, char *string);
Replaces string s with string in the specified text data object. 

int save_textdata(char **tdo, char *filename, int method);
Saves a text data object to a file. "Method" can be one of the following: 

TD_ZERO 
Strings will be zero delimited 
TD_NEWLINE 
Strings will be newline delimited 
TD_COMMA 
Strings will be enclosed in quotes and comma delimited 

void **sort_textdata(char ** tdo, int method);
Sorts the text data object. "Method" can be one of the following: 

SORT_ASCENDING 
Sorts in ascending order by ASCII codes 
SORT_DESCENDING 
Sorts in descending order by ASCII codes 
SORT_ASCENDING_NOCASE 
Sorts in ascending order, ignoring case 
SORT_DESCENDING_NOCASE 
Sorts in descending order, ignoring case 

char **concatenate_textdata(char ** tdo1, char ** tdo2);
Concatenates tdo2 to tdo1, destroying tdo1. 

void destroy_textdata(void *data);
Destroys a text data object.

void *load_textdata_object(PACKFILE *f, long size);
Loads a text data object from an open file (used when loading datafiles).

char **insert_textdata_string(char **tdo, char *string, int s);
Inserts a string in a text data object at position s, moving all higher strings up.

Usage Note:
Manipulating text data objects may cause their locations to change, hence you should always update the pointer for the text data object for functions that return a pointer, like so:

tdo=add_textdata_string(tdo,"I'm a little teapot, short and stout."); 
How do I prepare a text data object?
For a simple text data object, simply use your favorite editor to create a list of strings, one on each line. 

Now, create a new object in your data file using Allegro's grabber. Select Object from the grabber menu, then New, then Other. Enter the object type as "TEXT", and name the object. Finally, grab your text file and you're all set.

For long text data objects, you can define labels for easy reference to the strings in your code. This is useful when your strings represent messages, for example, where you may want a separate text data object for each language you support, loading the appropriate one at runtime.

In order to use labels, you'll need to use the MAKETEXT utility included with the library. To label your strings, first choose a character to act as a delimiter between your labels and your strings. This character must not appear in any of the strings that make up the TDO. (I like to use the tilde character (~); it rarely appears in any of my strings.)

Now that you have your delimiter, simply enter the label followed by the delimiter before the string you want to label. Thus, your text file might look like this: 

DISKFULL~Error: Out of disk space; cannot save file.
Please delete some files before continuing.
NOCREATE~Error: Cannot create %s.
OVERWRITE~File exists.  Overwrite?

(Note: Do not try to format this file up by indenting the strings that have no labels; spaces at the beginning and end of the strings are saved in the TDO!)

Now, to create the text data object, add the delimiter to your command line:
MAKETEXT DISKERR.TXT ~


This will create two files: a DISKERR.TDO containing the text data object, and a DISKERR.H containing the labels.

How do I use a text data object?
Loading an Allegro datafile containing a text data object will automatically build the text data object in memory. For easiest usage, you should assign the address of the text data object's data member to a pointer to pointer to char. This will allow you to treat the pointer as any other array of strings.

Example
Here's an example, using the DISKERR.TDO we just created and imported into DATAFILE.DAT. 

#include <stdio.h>
#include <stdlib.h>
#include <allegro.h>
#include <textdata.h>
#include "diskerr.h"

int main(void)
{
   DATAFILE *data;
   char **message;
   char filename[13]="badfile.txt";   

   allegro_init();
   install_textdata();
   data=load_datafile("datafile.dat");
   message=data[DISKERR]->dat;
   
   printf("%s\n%s\n",message[DISKFULL],message[DISKFULL+1]);
   printf(message[NOCREATE],filename);
   printf("\n");

   return(0);
}

Limitations of MAKETEXT.EXE
The line above that printed the NOCREATE message is an example of a limitation of MAKETEXT: it does not allow for newline characters (or any other escape sequences) to be stored in the TDO. If you must have newlines in a string in your TDO, you must insert them by hand into the TDO file.

The other limitation of MAKETEXT.EXE is that it is limited to a maximum string size of 40K. That's 40K for each individual string, there's no limit on the total size of the TDO. If you need exceedingly large strings in your program, build the TDO by hand or modify MAKETEXT.C to allow a larger string size.

Putting TDO's to work: HANGMAN
I have put together a simple game to illustrate the use of text data objects: HANGMAN. You've played the game hundreds of times as a child with paper and pencil; the object, as always, is to guess a word before a gallows can be drawn, with you dangling from it.

All of the strings in HANGMAN are contained in a text data object in an Allegro datafile (with the obvious exception of the error messages warning of a failure to load the datafile itself). Thus, the game is easily translated to foreign languages: simply build a datafile for each language. The game itself will not need to be re-compiled. (In this case, each language's datafile must have the same number of words; if the message text was moved to the beginning of wordlist.txt, and a little additional math was added to determine the range of the word list, then any number of words would work in each datafile.

Conclusion
These routines are free for your use. There are no license requirements if you want to use these routines; if you want to give me a credit somewhere, though, I won't argue. On the other hand, if you use these routines, I am not responsible if they wipe your hard drive, develop annoying habits, or sleep with your wife.

If you have any suggestions for or comments about these routines, they are welcome. And Shawn, you are welcome to assimilate them into Allegro proper.

If you need assistance or have any problems with the library, please contact me at celamantia@comcast.net. 
