
/* 
 * Committee for Graphics Arts Technologies Standards
 * CGATS.5 and IT8.7 family file I/O class
 * Version 2.05
 */

/*
 * Author: Graeme W. Gill
 * Date:   20/12/95
 *
 * Copyright 1995, 1996, 2002, Graeme W. Gill
 * All rights reserved.
 * 
 * This material is licensed with an "MIT" free use license:-
 * see the License4.txt file in this directory for licensing details.
 */


/*

	Should add a function to promote a field type, ie.
	promote integer to float, to avoid misrecognition
	problems.


	To make this more portable for independent use,
	should save/set/restore LC_NUMERIC locale before
	printf/scanf from file. e.g.

		include <locale.h>
		char *old_locale, *saved_locale;
     
		old_locale = setlocale (LC_NUMERIC, NULL);
		saved_locale = strdup (old_locale);
		if (saved_locale == NULL)
			error ("Out of memory");
		setlocale (LC_NUMERIC, "C");

		.... read or write ...
     
		setlocale (LC_NUMERIC, saved_locale);
		free (saved_locale);

	Also apply to pars.c
 */

#define _CGATS_C_				/* Turn on implimentation code */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#undef DEBUG				/* Debug only in slected places */

#ifdef DEBUG
# define DBGA stderr
# define DBGF(xx)	fprintf xx
#else
# define DBGF(xx)
#endif

#ifdef STANDALONE_TEST
extern void error(const char *fmt, ...), warning(const char *fmt, ...);
#endif

#include <sys/types.h>
#include <time.h>
#include <string.h>
#include <math.h>
#include "pars.h"
#include "cgats.h"

#undef  EMIT_KEYWORDS		/* [und] Emit unknown keywords by default */
#define REAL_SIGDIG 6		/* [6] Number of significant digits in real representation */

static int cgats_read(cgats *p, cgatsFile *fp);
static int find_kword(cgats *p, int table, const char *ksym);
static int find_field(cgats *p, int table, const char *fsym);
static int add_table(cgats *p, table_type tt, int oi);
static int set_table_flags(cgats *p, int table, int sup_id, int sup_kwords, int sup_fields);
static int set_cgats_type(cgats *p, const char *osym);
static int add_other(cgats *p, const char *osym);
static int get_oi(cgats *p, const char *osym);
static int add_kword(cgats *p, int table, const char *ksym, const char *kdata, const char *kcom);
static int add_field(cgats *p, int table, const char *fsym, data_type ftype);
static int add_set(cgats *p, int table, ...);
static int add_setarr(cgats *p, int table, cgats_set_elem *args);
static int get_setarr(cgats *p, int table, int set_index, cgats_set_elem *args);
static int cgats_write(cgats *p, cgatsFile *fp);
static int cgats_error(cgats *p, char **mes);
static void cgats_del(cgats *p);

static void cgats_table_free(cgats_table *t);
static void *alloc_copy_data_type(cgatsAlloc *al, data_type ktype, void *dpoint);
static int reserved_kword(const char *ksym);
static int standard_kword(const char *ksym);
static data_type standard_field(const char *fsym);
static int cs_has_ws(const char *cs);
static char *quote_cs(cgatsAlloc *al, const char *cs);
static int clear_fields(cgats *p, int table);
static int add_kword_at(cgats *p, int table, int  pos, const char *ksym, const char *kdatak, const char *kcom);
static int add_data_item(cgats *p, int table, void *data);
static void unquote_cs(char *cs);
static data_type guess_type(const char *cs);
static void real_format(double value, int nsd, char *fmt);

#ifdef COMBINED_STD
static int cgats_read_name(cgats *p, const char *filename);
static int cgats_write_name(cgats *p, const char *filename);
#endif

static const char *data_type_desc[] =
	{ "real", "integer", "char string", "non-quoted char string", "no type" };

/* Create an empty cgats object */
/* Return NULL on error */
cgats *new_cgats_al(
cgatsAlloc *al			/* memory allocator */
) {
	cgats *p;

	if ((p = (cgats *) al->calloc(al, sizeof(cgats), 1)) == NULL) {
		return NULL;
	}
	p->al = al;				/* Heap allocator */

	/* Initialize the methods */
	p->find_kword = find_kword;
	p->find_field = find_field;
	p->read       = cgats_read;
	p->add_table  = add_table;
	p->set_table_flags = set_table_flags;
	p->set_cgats_type  = set_cgats_type;
	p->add_other  = add_other;
	p->get_oi     = get_oi;
	p->add_kword  = add_kword;
	p->add_field  = add_field;
	p->add_set    = add_set;
	p->add_setarr = add_setarr;
	p->get_setarr = get_setarr;
	p->write      = cgats_write;
	p->error      = cgats_error;
	p->del        = cgats_del;
	
#ifndef SEPARATE_STD
	p->read_name  = cgats_read_name;
	p->write_name = cgats_write_name;
#else
	p->read_name  = NULL;
	p->write_name = NULL;
#endif

#ifdef EMIT_KEYWORDS
	p->emit_keywords = 1;
#endif

	return p;
}

static int err(cgats *p, int errc, const char *fmt, ...);

/* new_cgats() with default malloc allocator */

#ifndef SEPARATE_STD
#define COMBINED_STD

#include "cgatsstd.c"

#undef COMBINED_STD
#endif /* SEPARATE_STD */

/* ------------------------------------------- */

/* Implimentation function - register an error */
/* Return the error number */
static int
err(cgats *p, int errc, const char *fmt, ...) {
	va_list args;

	p->errc = errc;
	va_start(args, fmt);
	vsprintf(p->err, fmt, args);
	va_end(args);

	/* If this is the first registered error */
	if (p->ferrc != 0) {
		p->ferrc = p->errc;
		strcpy(p->ferr, p->err);
	}

	return errc;
}

/* Define methods */

/* Return error code and message */
/* for the first error, if any error */
/* has occured since object creation. */
static int cgats_error(
cgats *p,
char **mes
) {
	if (p->ferrc != 0) {
		if (mes != NULL)
			*mes = p->ferr;
		return p->ferrc;
	}
	return 0;
}

/* ------------------------------------------- */

/* Free the cgats object */
static void
cgats_del(cgats *p) {
	int i;
	cgatsAlloc *al = p->al;
	int del_al     = p->del_al;

	/* Free all the user defined file identifiers */
	if (p->cgats_type != NULL) {
		al->free(al, p->cgats_type);
	}

	if (p->others != NULL) {
		for (i = 0; i < p->nothers; i++)
			if(p->others[i] != NULL)
				al->free(al, p->others[i]);
		al->free(al, p->others);
	}

	/* Free contents of all the tables */
	for (i = 0; i < p->ntables; i++)
		cgats_table_free(&p->t[i]);

	/* Free the table structures */
	if (p->t != NULL)
		al->free(al, p->t);

	al->free(al, p);

	if (del_al)			/* We are responsible for deleting allocator */
		al->del(al);
}

/* Free up the contents of a cgats_table struct */
static void
cgats_table_free(cgats_table *t) {
	cgatsAlloc *al = t->al;
	int i,j;

	/* Free all the keyword symbols */
	if (t->ksym != NULL) {
		for (i = 0; i < t->nkwords; i++)
			if(t->ksym[i] != NULL)
				al->free(al, t->ksym[i]);
		al->free(al, t->ksym);
	}
	/* Free all the keyword values */
	if (t->kdata != NULL) {
		for (i = 0; i < t->nkwords; i++)
			if(t->kdata[i] != NULL)
				al->free(al, t->kdata[i]);
		al->free(al, t->kdata);
	}
	/* Free all the keyword comments */
	if (t->kcom != NULL) {
		for (i = 0; i < t->nkwords; i++)
			if(t->kcom[i] != NULL)
				al->free(al, t->kcom[i]);
		al->free(al, t->kcom);
	}

	/* Free all the field symbols */
	if (t->fsym != NULL) {
		for (i = 0; i < t->nfields; i++)
			if(t->fsym[i] != NULL)
				al->free(al, t->fsym[i]);
		al->free(al, t->fsym);
	}
	/* Free array of field types */
	if (t->ftype != NULL)
		al->free(al, t->ftype);
	/* Free all the original fields text values */
	if (t->rfdata != NULL) {
		for (j = 0; j < t->nsets; j++)
			if (t->rfdata[j] != NULL) {
				for (i = 0; i < t->nfields; i++)
					if(t->rfdata[j][i] != NULL)
						al->free(al, t->rfdata[j][i]);
				al->free(al, t->rfdata[j]);
			}
		al->free(al, t->rfdata);
	}
	/* Free all the fields values */
	if (t->fdata != NULL) {
		for (j = 0; j < t->nsets; j++)
			if (t->fdata[j] != NULL) {
				for (i = 0; i < t->nfields; i++)
					if(t->fdata[j][i] != NULL)
						al->free(al, t->fdata[j][i]);
				al->free(al, t->fdata[j]);
			}
		al->free(al, t->fdata);
	}
}

/* Return index of the keyword, -1 on fail */
/* -2 on illegal table index, message in err & errc */
static int
find_kword(cgats *p, int table, const char *ksym) {
	int i;
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';

	if (table < 0 || table >= p->ntables)
		return err(p, -2, "cgats.find_kword(), table number '%d' is out of range",table);
	t = &p->t[table];

	if (ksym == NULL || ksym[0] == '\000')
		return -1;

	for (i = 0; i < t->nkwords; i ++) {
		if (t->ksym[i] != NULL && t->kdata[i] != NULL
		    && strcmp(t->ksym[i],ksym) == 0)
			return i;
	}

	return -1;
}

/* Return index of the field, -1 on fail */
/* -2 on illegal table index, message in err & errc */
static int
find_field(cgats *p, int table, const char *fsym) {
	int i;
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';

	if (table < 0 || table >= p->ntables)
		return err(p, -2, "cgats.find_field(), table number '%d' is out of range",table);
	t = &p->t[table];

	if (fsym == NULL || fsym[0] == '\000')
		return -1;

	for (i = 0; i < t->nfields; i ++)
		if (strcmp(t->fsym[i],fsym) == 0)
			return i;

	return -1;
}

/* Read a cgats file into structure */
/* returns 0 normally, -ve if there was an error, */
/* and p->errc and p->err will be valid */
static int
cgats_read(cgats *p, cgatsFile *fp) {
	parse *pp;
/* Read states */
#define R_IDENT 0		/* Reading file identifier */
#define R_KWORDS 1		/* Reading keywords */
#define R_KWORD_VALUE 2	/* Reading keywords values */
#define R_FIELDS 3		/* Reading field declarations */
#define R_DATA 4		/* Reading data in set */
	int rstate = R_IDENT;
	int tablef = 0;		/* Current table we should be filling */
	int expsets = 0;	/* Expected number of sets */
	char *kw = NULL;	/* keyword symbol */

	p->errc = 0;
	p->err[0] = '\000';

	if ((pp = new_parse_al(p->al, fp)) == NULL) {
		DBGF((DBGA,"Failed to open parser for file\n"));
		return err(p, -1, "Unable to create file parser for file '%s'",fp->fname(fp));
	}

	/* Setup our token parsing charaters */
	/* Terminators, Not Read, Comment start, Quote characters */
	pp->add_del(pp, " \t"," \t", "#", "\"");

	/* Read in the file */
	for (;;) {
		char *tp;			/* Token string */

		/* Fetch the next token */
		while ((tp = pp->get_token(pp)) == NULL) {
			int rc;
			if (pp->errc != 0) {		/* get_token got an error */
				err(p, -1, "%s", pp->err);
				pp->del(pp);
				DBGF((DBGA,"Get token got error '%s'\n",pp->err));
				return p->errc;
			}
			if ((rc = pp->read_line(pp)) == 0)
				break;		/* End of file */
			else if (rc == -1) {		/* read_line got an error */
				err(p, -1, "%s", pp->err);
				pp->del(pp);
				DBGF((DBGA,"Read line got error '%s'\n",pp->err));
				return p->errc;
			}
		}
		if (tp == NULL)
			break;		/* EOF */

		/* This seems unlikely and will cause err() to barf */
		if (strlen(tp) > CGATS_ERRM_LENGTH/2) {
			tp[CGATS_ERRM_LENGTH/2] = '\000';
			err(p,-1,"Read line got symbol '%s' that's too long\n",tp);
			pp->del(pp);
			return p->errc;
		}

		switch(rstate) {
			case R_IDENT: 		/* Expecting file identifier */
			case R_KWORDS: {	/* Expecting keyword, field def or data */
				table_type tt = tt_none;
				int oi = 0;		/* Index if tt_other */

				DBGF((DBGA,"Got kword '%s'\n",tp));
				if (rstate == R_IDENT) {
					DBGF((DBGA,"Expecting file identifier\n"));
				}

				/* The standard says that keywords have to be at the start of a line */
				if (pp->token != 1)			/* Be robust and ignore any problems */
					break;

				/* See if we have a file identifier */
				if(strcmp(tp,"IT8.7/1") == 0)
					tt = it8_7_1;
				else if(strcmp(tp,"IT8.7/2") == 0)
					tt = it8_7_2;
				else if(strcmp(tp,"IT8.7/3") == 0)
					tt = it8_7_3;
				else if(strcmp(tp,"IT8.7/4") == 0)
					tt = it8_7_4;
				else if(strcmp(tp,"CGATS.5") == 0)
					tt = cgats_5;
				else if(strncmp(tp,"CGATS.",6) == 0) {	/* Variable CGATS type */
					tt = cgats_X;
					if (p->cgats_type != NULL)
						p->al->free(p->al, p->cgats_type);
					if ((p->cgats_type = (char *)p->al->malloc(p->al,
					                     (strlen(tp)+1) * sizeof(char))) == NULL) {
						err(p,-1,"Failed to malloc space for CGATS.X keyword");
						pp->del(pp);
						return p->errc;
					}
					strcpy(p->cgats_type,tp);
					DBGF((DBGA,"Found CGATS file identifier\n"));
					rstate = R_KWORDS;
				} else {	/* See if it is an 'other' file identifier */
					int iswild = 0;	
					DBGF((DBGA,"Checking for 'other' identifier\n"));

					/* Check for non-wildcard "other" */
					for (oi = 0; oi < p->nothers; oi++) {

						if (p->others[oi][0] == '\000') {	/* Wild card */
							iswild = 1;
							continue;
						}
						/* If "other" is a specific string */
						if(strcmp(tp,p->others[oi]) == 0) {
							DBGF((DBGA,"Matches 'other' %s\n",p->others[oi]));
							tt = tt_other;
							rstate = R_KWORDS;
							break;
						}
					}

					if (tt == tt_none
					 && iswild
					 && rstate == R_IDENT				/* First token after a table */
					 && standard_kword(tp) == 0			/* And not an obvious kword */
					 && reserved_kword(tp) == 0) {
						DBGF((DBGA,"Matches 'other' wildcard\n"));
						if ((oi = add_other(p, tp)) == -2) {
							pp->del(pp);
							DBGF((DBGA,"add_other for wilidcard failed\n"));
							return p->errc;
						}
						tt = tt_other;
						rstate = R_KWORDS;
					}
				}

				/* First ever token must be file identifier */
				if (tt == tt_none && p->ntables == 0) {
					err(p,-1,"Error at line %d of file '%s': No CGATS file identifier found",pp->line,fp->fname(fp));
					pp->del(pp);
					DBGF((DBGA,"Failed to match file identifier\n"));
					return p->errc;
				}

				/* Any token after previous table has data finished */
				/* causes a new table to be created. */
				if (p->ntables == tablef) {

					if (tt != tt_none) {			/* Current token is a file identifier */
						DBGF((DBGA,"Got file identifier, adding plain table\n"));
	        			if (add_table(p, tt, oi) < 0) {
							pp->del(pp);
							DBGF((DBGA,"Add table failed\n"));
							return p->errc;
						}
					} else {	/* Carry everything over from previous table the table type */
						int i;
						cgats_table *pt;
						int ct;

						DBGF((DBGA,"No file identifier, adding table copy of previous\n"));

	        			if (add_table(p, p->t[p->ntables-1].tt, p->t[p->ntables-1].oi) < 0) {
							pp->del(pp);
							DBGF((DBGA,"Add table failed\n"));
							return p->errc;
						}

						pt = &p->t[p->ntables-2];
						ct = p->ntables-1;

						for (i = 0; i < pt->nkwords; i++) {
							if (p->add_kword(p, ct, pt->ksym[i], pt->kdata[i], pt->kcom[i]) < 0) {
								pp->del(pp);
								DBGF((DBGA,"Add keyword failed\n"));
								return p->errc;
							}
						}
						for (i = 0; i < pt->nfields; i++)
							if (p->add_field(p, ct, pt->fsym[i], none_t) < 0) {
								pp->del(pp);
								DBGF((DBGA,"Add field failed\n"));
								return p->errc;
							}
					}
				}

				/* If not a file identifier */
				if (tt == tt_none) {
					/* See if we're starting the field declarations */
					if(strcmp(tp,"BEGIN_DATA_FORMAT") == 0) {
						rstate = R_FIELDS;
						if (clear_fields(p, p->ntables-1) < 0) {
							pp->del(pp);
							DBGF((DBGA,"Clear field failed\n"));
							return p->errc;
						}
						break;
					}
					if(strcmp(tp,"SAMPLE_ID") == 0) {	/* Faulty table - cope gracefully */
						rstate = R_FIELDS;
						if (clear_fields(p, p->ntables-1) < 0) {
							pp->del(pp);
							DBGF((DBGA,"Clear field failed\n"));
							return p->errc;
						}
						goto first_field;
					}

					if(strcmp(tp,"BEGIN_DATA") == 0) {
						rstate = R_DATA;
						break;
					}
					/* Else must be a keyword */
					if ((kw = (char *)alloc_copy_data_type(p->al, cs_t, (void *)tp)) == NULL) {
						err(p, -2, "cgats.alloc_copy_data_type() malloc fail");
						pp->del(pp);
						DBGF((DBGA,"Alloc data type failed\n"));
						return p->errc;
					}
					rstate = R_KWORD_VALUE;
				}
				break;
			}
			case R_KWORD_VALUE: {
				/* Add a keyword and its value */
				
				DBGF((DBGA,"Got keyword value '%s'\n",kw));

				/* Special case for read() use */
				if(strcmp(kw,"NUMBER_OF_SETS") == 0)
					expsets = atoi(tp);

				if (!reserved_kword(kw)) {	/* Don't add reserved keywords */
					int ix;

					/* Replace keyword if it already exists */
					unquote_cs(tp);
					if ((ix = find_kword(p, p->ntables-1, kw)) < -1) {
						pp->del(pp);
						DBGF((DBGA,"Failed to find keyword\n"));
						return p->errc;
					}
					if (add_kword_at(p, p->ntables-1, ix, kw, tp, NULL) < 0) {
						pp->del(pp);
						DBGF((DBGA,"Failed to add keyword '%s'\n",kw));
						return p->errc;
					}
				}
				p->al->free(p->al, kw);
				rstate = R_KWORDS;
				break;
			}
			case R_FIELDS: {
				DBGF((DBGA,"Got fields value '%s'\n",tp));

				/* Add a list of field name declarations */
				if(strcmp(tp,"END_DATA_FORMAT") == 0) {
					rstate = R_KWORDS;
					break;
				}
				if(strcmp(tp,"BEGIN_DATA") == 0) {	/* Faulty table - cope gracefully */
					rstate = R_DATA;
					break;
				}
				if(strcmp(tp,"DEVICE_NAME") == 0) {	/* Faulty CB table - cope gracefully */
					/* It's unlikely anyone will use DEVICE_NAME as a field name */
					/* Assume this is a keyword */
					if ((kw = (char *)alloc_copy_data_type(p->al, cs_t, (void *)tp)) == NULL) {
						err(p, -2, "cgats.alloc_copy_data_type() malloc fail");
						pp->del(pp);
						DBGF((DBGA,"Alloc data type failed\n"));
						return p->errc;
					}
					rstate = R_KWORD_VALUE;
					break;
				}
			  first_field:;	/* Direct leap - cope with faulty table */
				if (p->add_field(p, p->ntables-1, tp, none_t) < 0)	/* none == cs untill figure type */ {
					pp->del(pp);
					DBGF((DBGA,"Add field failed\n"));
					return p->errc;
				}
				break;
			}
			case R_DATA: {
				cgats_table *ct = &p->t[p->ntables-1];

				DBGF((DBGA,"Got data value '%s'\n",tp));
				if(strcmp(tp,"END_DATA") == 0) {
					int i,j;
#ifdef NEVER
					if (ct->nsets == 0) {
						err(p,-1,"Error at line %d of file '%s': End of data without any data being read",pp->line,fp->fname(fp));
						pp->del(pp);
						DBGF((DBGA,"End of data without any data being read\n"));
						return p->errc;
					}
#endif // NEVER
					if (expsets != 0 && ct->nsets != expsets) {
						err(p,-1,"Error at line %d of file '%s': Read %d sets, expected %d sets",pp->line,fp->fname(fp),ct->nsets,expsets);
						pp->del(pp);
						DBGF((DBGA,"End of mimatch in number of sets\n"));
						return p->errc;
					}
					if (ct->ndf != 0) {
						err(p,-1,"Error at line %d of file '%s': Data was not an integer multiple of fields (remainder %d out of %d)",pp->line,fp->fname(fp),ct->ndf,ct->nfields);
						pp->del(pp);
						DBGF((DBGA,"Not an interger multiple of fields\n"));
						return p->errc;
					}

					/* We now need to determine the data types */
					/* and convert them appropriately */
					for (i = 0; i < ct->nfields; i++) {
						data_type bt = i_t, st;
						for (j = 0; j < ct->nsets; j++) {
							data_type ty;
							ty = guess_type(((char *)ct->rfdata[j][i]));

							if (ty == cs_t) {
								bt = cs_t;
								break;		/* Early out */
							} else if (ty == nqcs_t) {
								if (bt == i_t || bt == r_t)
									bt = ty;
							} else if (ty == r_t) {
								if (bt == i_t)
									bt = ty;
							} else { /* ty == i_t */
								/* This is the default */
							}
						}
						/* Got guessed type bt. Sanity check against known field types */
						/* and promote if that seems reasonable */
						st = standard_field(ct->fsym[i]);
						if ((st == r_t && bt == i_t)		/* If ambiguous, use standard field */
						 || ((st == cs_t || st == nqcs_t) && bt == i_t)	/* Promote any to string */
						 || ((st == cs_t || st == nqcs_t) && bt == r_t)	/* Promote any to string */
						 || (st == nqcs_t && bt == cs_t)
						 || (st == cs_t && bt == nqcs_t))
							bt = st;

						/* If standard type doesn't match what it should, throw an error */
						if (st != none_t && st != bt) {
							err(p, -1,"Error in file '%s': Field '%s' has unexpected type, should be '%s', is '%s'",fp->fname(fp),ct->fsym[i],data_type_desc[st],data_type_desc[bt]);
							pp->del(pp);
							DBGF((DBGA,"Standard field has unexpected data type\n"));
							return p->errc;
						}

						/* Set field type, and then convert the fields to correct type. */
						ct->ftype[i] = bt;
						for (j = 0; j < ct->nsets; j++) {
							switch(bt) {
								case r_t: {
									double dv;
									dv = atof((char *)ct->rfdata[j][i]);
									if ((ct->fdata[j][i] = alloc_copy_data_type(p->al, bt, (void *)&dv)) == NULL) {
										err(p, -2, "cgats.alloc_copy_data_type() malloc fail");
										pp->del(pp);
										DBGF((DBGA,"Alloc copy data type failed\n"));
										return p->errc;
									}
									break;
								}
								case i_t: {
									int iv;
									iv = atoi((char *)ct->rfdata[j][i]);
									if ((ct->fdata[j][i] = alloc_copy_data_type(p->al, bt, (void *)&iv)) == NULL) {
										err(p, -2, "cgats.alloc_copy_data_type() malloc fail");
										pp->del(pp);
										DBGF((DBGA,"Alloc copy data type failed\n"));
										return p->errc = -2;
									}
									break;
								}
								case cs_t:
								case nqcs_t: {
									char *cv;
									cv = ct->rfdata[j][i];
									if ((ct->fdata[j][i]
= alloc_copy_data_type(p->al, bt, (void *)cv)) == NULL) {
										err(p, -2, "cgats.alloc_copy_data_type() malloc fail");
										pp->del(pp);
										DBGF((DBGA,"Alloc copy data type failed\n"));
										return p->errc = -2;
									}
									unquote_cs((char *)ct->fdata[j][i]);
									break;
								}
								case none_t:
									break;
							}
						}
					}
					
					tablef = p->ntables;	/* Finished data for current table */
					rstate = R_IDENT;
					break;
				}

				/* Make sure fields have been decalared */
				if (ct->nfields == 0) {
					err(p, -1,"Error at line %d of file '%s': Found data without field definitions",pp->line,fp->fname(fp));
					pp->del(pp);
					DBGF((DBGA,"Found data without field definition\n"));
					return p->errc;
				}
				/* Add the data item */
				if (add_data_item(p, p->ntables-1, tp) < 0) {
					pp->del(pp);
					DBGF((DBGA,"Adding data item failed\n"));
					return p->errc;
				}
				break;
			}
		}
	}

	pp->del(pp);		/* Clean up the parse file */

	if (p->ntables == 0)
		return -1;		/* Failed to load any table */

	return 0;
}

/* Define the (one) variable CGATS type */
/* Return -2 & set errc and err on system error */
static int
set_cgats_type(cgats *p, const char *osym) {
	cgatsAlloc *al = p->al;

	p->errc = 0;
	p->err[0] = '\000';
	if (p->cgats_type != NULL)
		al->free(al, p->cgats_type);
	if ((p->cgats_type = (char *)al->malloc(al, (strlen(osym)+1) * sizeof(char))) == NULL)
		return err(p,-2,"cgats.add_cgats_type(), malloc failed!");
	strcpy(p->cgats_type,osym);
	return 0;
}

/* Add an 'other' file identifier string, and return the oi. */
/* Use a zero length string to indicate a wildcard. */
/* Return -2 & set errc and err on system error */
static int
add_other(cgats *p, const char *osym) {
	cgatsAlloc *al = p->al;

	p->errc = 0;
	p->err[0] = '\000';
	p->nothers++;
	if ((p->others = (char **)al->realloc(al, p->others, p->nothers * sizeof(char *))) == NULL)
		return err(p,-2, "cgats.add_other(), realloc failed!");
	if ((p->others[p->nothers-1] =
	                      (char *)al->malloc(al, (strlen(osym)+1) * sizeof(char))) == NULL)
		return err(p,-2,"cgats.add_other(), malloc failed!");
	strcpy(p->others[p->nothers-1],osym);
	return p->nothers-1;
}

/* Return the oi of the given other type */
/* return -ve and errc and err set on error */
static int get_oi(cgats *p, const char *osym) {
	int oi;

	p->errc = 0;
	p->err[0] = '\000';

	for (oi = 0; oi < p->nothers; oi++) {
		if (strcmp(p->others[oi], osym) == 0)
			return oi;
	}
	return err(p,-1,"cgats.get_oi(), failed to find '%s'!",osym);
}

/* Add a new (empty) table to the structure */
/* Return the index of the table. */
/* tt defines the table type, and oi is used if tt = tt_other */
/* Return -2 & set errc and err on system error */
static int
add_table(cgats *p, table_type tt, int oi) {
	cgatsAlloc *al = p->al;
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';
	p->ntables++;
	if ((p->t = (cgats_table *) al->realloc(al, p->t, p->ntables * sizeof(cgats_table))) == NULL)
		return err(p,-2, "cgats.add_table(), realloc failed!");
	memset(&p->t[p->ntables-1],0,sizeof(cgats_table));
	t = &p->t[p->ntables-1];

	t->al = al;		/* Pointer to allocator */
	t->tt = tt;
	t->oi = oi;

	return p->ntables-1;
}

/* set or reset table flags */
/* The sup_id flag suppreses the writing of the file identifier string for the table */
/* The sup_kwords flag suppreses the writing of the standard keyword definitions for the table */
/* The sup_fields flag suppreses the writing of the field definitions for the table */
/* The assumption is that the previous tables id and/or fields will be correct for */
/* the table that have these flags set. */
/* Return -1 & set errc and err on error */
static int set_table_flags(cgats *p, int table, int sup_id, int sup_kwords, int sup_fields) {
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables)
		return err(p,-1, "cgats.set_table_flags(), table number '%d' is out of range",table);
	t = &p->t[table];

	if (sup_id == 0 && (sup_kwords != 0 || sup_fields != 0))
		return err(p,-1, "cgats.set_table_flags(), Can't suppress kwords or fields if ID is not suppressed");
	t->sup_id = sup_id;
	t->sup_kwords = sup_kwords;
	t->sup_fields = sup_fields;

	return 0;
}


/* Append a new keyword/value + optional comment pair to the table */
/* If no comment is provided, kcom should be set to NULL */
/* A comment only line can be inserted amongst the keywords by providing */
/* NULL values for ksym and kdata, and the comment in kcom */
/* Return the index of the new keyword, or -1, err & errc on error */
static int
add_kword(cgats *p, int table, const char *ksym, const char *kdata, const char *kcom) {
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables)
		return err(p,-1, "cgats.add_kword(), table number '%d' is out of range",table);
	t = &p->t[table];

	return add_kword_at(p, table, t->nkwords, ksym, kdata, kcom);
}

/* Replace or append a new keyword/value pair + optional comment */
/* to the table in the given position. The keyword will be appended */
/* if it is < 0. */
/* If no comment is provided, kcom should be set to NULL */
/* A comment only line can be inserted amongst the keywords by providing */
/* NULL values for ksym and kdata, and the comment in kcom */
/* Return the index of the keyword, or -1, err & errc on error */
static int
add_kword_at(cgats *p, int table, int pos, const char *ksym, const char *kdata, const char *kcom) {
	cgatsAlloc *al = p->al;
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables) {
		DBGF((DBGA,"add_kword_at: table is invalid\n"));
		return err(p,-1, "cgats.add_kword(), table number '%d' is out of range",table);
	}
	t = &p->t[table];

	if (ksym != NULL && cs_has_ws(ksym)) {	/* oops */
		DBGF((DBGA,"add_kword_at: keyword '%s' is illegal (embedded white space, quote or comment character)\n",ksym));
		return err(p,-1, "cgats.add_kword(), keyword '%s'is illegal",ksym);
	}

	if (ksym != NULL && reserved_kword(ksym)) { 	/* oops */
		DBGF((DBGA,"add_kword_at: keyword '%s' is illegal (reserved)\n",ksym));
		return err(p,-1, "cgats.add_kword(), keyword '%s'is generated automatically",ksym);
	}

	if (pos < 0 || pos >= t->nkwords) {	/* This is an append */
		t->nkwords++;
		if (t->nkwords > t->nkwordsa) {	/* Allocate keyword pointers in groups of 8 */
			t->nkwordsa += 8;
			if ((t->ksym = (char **)al->realloc(al, t->ksym, t->nkwordsa * sizeof(char *))) == NULL)
				return err(p,-2, "cgats.add_kword(), realloc failed!");
			if ((t->kdata = (char **)al->realloc(al, t->kdata, t->nkwordsa * sizeof(char *)))
			                                                                        == NULL)
				return err(p,-2, "cgats.add_kword(), realloc failed!");
			if ((t->kcom = (char **)al->realloc(al, t->kcom, t->nkwordsa * sizeof(char *)))
			                                                                      == NULL)
				return err(p,-2, "cgats.add_kword(), realloc failed!");
		}
		pos = t->nkwords-1;
	} else {	/* This is a replacement */
		if (t->ksym[pos] != NULL)
			al->free(al, t->ksym[pos]);
		if (t->kdata[pos] != NULL)
			al->free(al, t->kdata[pos]);
		if (t->kcom[pos] != NULL)
			al->free(al, t->kcom[pos]);
	}

	if (ksym != NULL) {
		if ((t->ksym[pos] = (char *)alloc_copy_data_type(al, cs_t, (void *)ksym)) == NULL)
			return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
	} else
		t->ksym[pos] = NULL;

	if (kdata != NULL) {
		if ((t->kdata[pos] = (char *)alloc_copy_data_type(al, cs_t, (void *)kdata)) == NULL)
			return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
	} else
		t->kdata[pos] = NULL;

	if (kcom != NULL) {
		if ((t->kcom[pos] = (char *)alloc_copy_data_type(al, cs_t, (void *)kcom)) == NULL)
			return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
	} else
		t->kcom[pos] = NULL;
	return pos;
}

/* Add a new field to the table */
/* Return the index of the field */
/* return -1 or -2, errc & err on error */
static int
add_field(cgats *p, int table, const char *fsym, data_type ftype) {
	cgatsAlloc *al = p->al;
	cgats_table *t;
	data_type st;

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables) {
		return err(p,-1,"cgats.add_field(), table parameter out of range");
	}
	t = &p->t[table];

	if (t->nsets != 0) {
		return err(p,-1,"cgats.add_field(), attempt to add field to non-empty table");
	}

	/* Check the field name is reasonable */
	if (cs_has_ws(fsym)) {
		return err(p,-1,"cgats.add_kword(), field name '%s'is illegal",fsym);
	}

	if (ftype == none_t)
		ftype = cs_t;					/* Fudge - unknown type yet, used for reads */
	else {
		/* Check that the data type is reasonable */
		st = standard_field(fsym);
		if (st == nqcs_t && ftype == cs_t)	/* Fudge - standard type to non-quoted if normal */
			ftype = nqcs_t;
		if (st != none_t && st != ftype) {
			return err(p,-1,"cgats.add_field(): unexpected data type for standard field name");
		}
	}

	t->nfields++;
	if (t->nfields > t->nfieldsa) {
		/* Allocate fields in groups of 32 */
		t->nfieldsa += 32;
		if ((t->fsym = (char **)al->realloc(al, t->fsym, t->nfieldsa * sizeof(char *))) == NULL) {
			return err(p,-2,"cgats.add_field(), realloc failed!");
		}
		if ((t->ftype = (data_type *)al->realloc(al, t->ftype, t->nfieldsa * sizeof(data_type)))
		                                                                               == NULL) {
			return err(p,-2,"cgats.add_field(), realloc failed!");
		}
	}
	if ((t->fsym[t->nfields-1] = (char *)alloc_copy_data_type(al, cs_t, (void *)fsym)) == NULL) {
		return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
	}

	t->ftype[t->nfields-1] = ftype;

	return t->nfields-1;
}

/* Clear all fields in the table */
/* return 0 if OK */
/* return -1 or -2, errc & err on error */
static int
clear_fields(cgats *p, int table) {
	cgatsAlloc *al = p->al;
	int i;
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables)
		return err(p,-1,"cgats.clear_field(), table parameter out of range");
	t = &p->t[table];

	if (t->nsets != 0)
		return err(p,-1,"cgats.clear_field(), attempt to clear fields in a non-empty table");

	/* Free all the field symbols */
	if (t->fsym != NULL) {
		for (i = 0; i < t->nfields; i++)
			if(t->fsym[i] != NULL)
				al->free(al, t->fsym[i]);
		al->free(al, t->fsym);
		t->fsym = NULL;
	}

	/* Free array of field types */
	if (t->ftype != NULL)
		al->free(al, t->ftype);
	t->ftype = NULL;

	/* Zero all the field counters */
	t->nfields = 0;
	t->nfieldsa = 0;

	return 0;
}

/* Add a set of data */
/* return 0 normally. */
/* return -2, -1, errc & err on error */
static int
add_set(cgats *p, int table, ...) {
	cgatsAlloc *al = p->al;
	va_list args;
	int i;
	cgats_table *t;

	va_start(args, table);

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables)
		return err(p,-1,"cgats.add_kword(), table parameter out of range");
	t = &p->t[table];

	if (t->nfields == 0)
		return err(p,-1,"cgats.add_set(), attempt to add set when no fields are defined");

	t->nsets++;
	
	if (t->nsets > t->nsetsa) /* Allocate space for more sets */ {
		/* Allocate set pointers in groups of 100 */
		t->nsetsa += 100;
		if ((t->fdata = (void ***)al->realloc(al, t->fdata, t->nsetsa * sizeof(void **))) == NULL)
			return err(p,-2,"cgats.add_set(), realloc failed!");
	}
	/* Allocate set pointer to data element values */
	if ((t->fdata[t->nsets-1] = (void **)al->malloc(al, t->nfields * sizeof(void *))) == NULL)
		return err(p,-2,"cgats.add_set(), malloc failed!");

	/* Allocate and copy data to new set */
	for (i = 0; i < t->nfields; i++) {
		switch(t->ftype[i]) {
			case r_t: {
				double dv;
				dv = va_arg(args, double);
				if ((t->fdata[t->nsets-1][i] = alloc_copy_data_type(al, t->ftype[i], (void *)&dv)) == NULL)
					return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
				break;
			}
			case i_t: {
				int iv;
				iv = va_arg(args, int);
				if ((t->fdata[t->nsets-1][i] = alloc_copy_data_type(al, t->ftype[i], (void *)&iv)) == NULL)
					return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
				break;
			}
			case cs_t:
			case nqcs_t: {
				char *sv;
				sv = va_arg(args, char *);
				if ((t->fdata[t->nsets-1][i] = alloc_copy_data_type(al, t->ftype[i], (void *)sv)) == NULL)
					return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
				break;
			}
			default:
				return err(p,-1,"cgats.add_set(), field has unknown data type");
		}
	}
	va_end(args);

	return 0;
}

/* Add a set of data from void array */
/* (Courtesy of Neil Okamoto) */
/* return 0 normally. */
/* return -2, -1, errc & err on error */
static int
add_setarr(cgats *p, int table, cgats_set_elem *args) {
	cgatsAlloc *al = p->al;
	int i;
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables)
		return err(p,-1,"cgats.add_setarr(), table parameter out of range");
	t = &p->t[table];

	if (t->nfields == 0)
		return err(p,-1,"cgats.add_setarr(), attempt to add set when no fields are defined");

	t->nsets++;
	
	if (t->nsets > t->nsetsa) /* Allocate space for more sets */ {
		/* Allocate set pointers in groups of 100 */
		t->nsetsa += 100;
		if ((t->fdata = (void ***)al->realloc(al,t->fdata, t->nsetsa * sizeof(void **))) == NULL)
			return err(p,-2,"cgats.add_set(), realloc failed!");
	}
	/* Allocate set pointer to data element values */
	if ((t->fdata[t->nsets-1] = (void **)al->malloc(al,t->nfields * sizeof(void *))) == NULL)
		return err(p,-2,"cgats.add_set(), malloc failed!");

	/* Allocate and copy data to new set */
	for (i = 0; i < t->nfields; i++) {
		switch(t->ftype[i]) {
			case r_t: {
				double dv;
				dv = args[i].d;
				if ((t->fdata[t->nsets-1][i] = alloc_copy_data_type(al, t->ftype[i], (void *)&dv)) == NULL)
					return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
				break;
			}
			case i_t: {
				int iv;
				iv = args[i].i;
				if ((t->fdata[t->nsets-1][i] = alloc_copy_data_type(al, t->ftype[i], (void *)&iv)) == NULL)
					return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
				break;
			}
			case cs_t:
			case nqcs_t: {
				char *sv;
				sv = args[i].c;
				if ((t->fdata[t->nsets-1][i] = alloc_copy_data_type(al, t->ftype[i], (void *)sv)) == NULL)
					return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");
				break;
			}
			default:
				return err(p,-1,"cgats.add_set(), field has unknown data type");
		}
	}
	return 0;
}

/* Fill a suitable set_element with a set of data. */
/* Note a returned char pointer is to a string in *p */
/* return 0 normally. */
/* return -2, -1, errc & err on error */
static int
get_setarr(cgats *p, int table, int set_index, cgats_set_elem *args) {
	int i;
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables)
		return err(p,-1,"cgats.get_setarr(), table parameter out of range");
	t = &p->t[table];
	if (set_index < 0 || set_index >= t->nsets)
		return err(p,-1,"cgats.get_setarr(), set parameter out of range");

	for (i = 0; i < t->nfields; i++) {
		switch(t->ftype[i]) {
			case r_t:
				args[i].d = *((double *)t->fdata[set_index][i]);
				break;
			case i_t:
				args[i].i = *((int *)t->fdata[set_index][i]);
				break;
			case cs_t:
			case nqcs_t:
				args[i].c = ((char *)t->fdata[set_index][i]);
				break;
			default:
				return err(p,-1,"cgats.get_setarr(), field has unknown data type");
		}
	}
	return 0;
}

/* Add an item of data to rgdata[][] from the read file. */
/* return 0 normally. */
/* return -2, -1, errc & err on error */
static int
add_data_item(cgats *p, int table, void *data) {
	cgatsAlloc *al = p->al;
	cgats_table *t;

	p->errc = 0;
	p->err[0] = '\000';
	if (table < 0 || table >= p->ntables)
		return err(p,-1,"cgats.add_kword(), table parameter out of range");
	t = &p->t[table];

	if (t->nfields == 0)
		return err(p,-1,"cgats.add_item(), attempt to add data when no fields are defined");

	if (t->ndf == 0) {	/* We're about to do the first element of a new set */
		t->nsets++;
		
		if (t->nsets > t->nsetsa) { /* Allocate space for more sets */
			/* Allocate set pointers in groups of 100 */
			t->nsetsa += 100;
			if ((t->rfdata = (char ***)al->realloc(al, t->rfdata, t->nsetsa * sizeof(void **)))
			                                                                        == NULL)
				return err(p,-2,"cgats.add_item(), realloc failed!");
			if ((t->fdata = (void ***)al->realloc(al, t->fdata, t->nsetsa * sizeof(void **)))
			                                                                        == NULL)
				return err(p,-2,"cgats.add_item(), realloc failed!");
		}
		/* Allocate set pointer to data element values */
		if ((t->rfdata[t->nsets-1] = (char **)al->malloc(al, t->nfields * sizeof(void *))) == NULL)
			return err(p,-2,"cgats.add_item(), malloc failed!");
		if ((t->fdata[t->nsets-1] = (void **)al->malloc(al, t->nfields * sizeof(void *))) == NULL)
			return err(p,-2,"cgats.add_item(), malloc failed!");
	}

	/* Data type is always cs_t at this point, because we haven't decided the type */
	if ((t->rfdata[t->nsets-1][t->ndf] = alloc_copy_data_type(al, cs_t, data)) == NULL)
		return err(p,-2,"cgats.alloc_copy_data_type() malloc fail");

	if (++t->ndf >= t->nfields)
		t->ndf = 0;

	return 0;
}

/* Write structure into cgats file */
/* Return -ve, errc & err if there was an error */
static int
cgats_write(cgats *p, cgatsFile *fp) {
	cgatsAlloc *al = p->al;
	int i;
	int table,set,field;
	int *sfield = NULL;	/* Standard field flag */
	p->errc = 0;
	p->err[0] = '\000';

	DBGF((DBGA,"CGATS write called, ntables = %d\n",p->ntables));
	for (table = 0; table < p->ntables; table++) {
		cgats_table *t = &p->t[table];

		DBGF((DBGA,"CGATS writing table %d\n",table));

		/* Figure out the standard and non-standard fields */
		if (t->nfields > 0)
			if ((sfield = (int *)al->malloc(al, t->nfields * sizeof(int))) == NULL)
				return err(p,-2,"cgats.write(), malloc failed!");
		for (field = 0; field < t->nfields; field++) {
				if (standard_field(t->fsym[field]) != none_t)
					sfield[field] = 1;	/* Is standard */
				else
					sfield[field] = 0;
			}
	
		if (!t->sup_kwords)	/* If not suppressed */ {
			/* Make sure table has basic keywords */
			if ((i = p->find_kword(p,table,"ORIGINATOR")) < 0)	/* Create it */
				if (p->add_kword(p,table,"ORIGINATOR","Not specified", NULL) < 0) {
					al->free(al, sfield);
					return p->errc;
				}
			if ((i = p->find_kword(p,table,"DESCRIPTOR")) < 0)	/* Create it */
				if (p->add_kword(p,table,"DESCRIPTOR","Not specified", NULL) < 0) {
					al->free(al, sfield);
					return p->errc;
				}
			if ((i = p->find_kword(p,table,"CREATED")) < 0) {	/* Create it */
				static char *amonths[] = {"January","February","March","April",
				                          "May","June","July","August","September",
				                          "October","November","December"};
				time_t ctime;
				struct tm *ptm;
				char tcs[100];
				ctime = time(NULL);
				ptm = localtime(&ctime);
				sprintf(tcs,"%s %d, %d",amonths[ptm->tm_mon],ptm->tm_mday,1900+ptm->tm_year);
				if (p->add_kword(p,table,"CREATED",tcs, NULL) < 0) {
					al->free(al, sfield);
					return p->errc;
				}
			}
	
			/* And table type specific keywords */
			/* (Not sure this is correct - CGATS.5 appendix J is not specific enough) */
			switch(t->tt) {
				case it8_7_1:
				case it8_7_2:	/* Physical target reference files */
					if ((i = p->find_kword(p,table,"MANUFACTURER")) < 0)	/* Create it */
						if (p->add_kword(p,table,"MANUFACTURER","Not specified", NULL) < 0) {
							al->free(al, sfield);
							return p->errc;
						}
					if ((i = p->find_kword(p,table,"PROD_DATE")) < 0)	/* Create it */
						if (p->add_kword(p,table,"PROD_DATE","Not specified", NULL) < 0) {
							al->free(al, sfield);
							return p->errc;
						}
					if ((i = p->find_kword(p,table,"SERIAL")) < 0)	/* Create it */
						if (p->add_kword(p,table,"SERIAL","Not specified", NULL) < 0) {
							al->free(al, sfield);
							return p->errc;
						}
					if ((i = p->find_kword(p,table,"MATERIAL")) < 0)	/* Create it */
						if (p->add_kword(p,table,"MATERIAL","Not specified", NULL) < 0) {
							al->free(al, sfield);
							return p->errc;
						}
					break;
				case it8_7_3:	/* Target measurement files */
				case it8_7_4:
				case cgats_5:
				case cgats_X:
					if ((i = p->find_kword(p,table,"INSTRUMENTATION")) < 0)	/* Create it */
						if (p->add_kword(p,table,"INSTRUMENTATION","Not specified", NULL) < 0) {
							al->free(al, sfield);
							return p->errc;
						}
					if ((i = p->find_kword(p,table,"MEASUREMENT_SOURCE")) < 0)	/* Create it */
						if (p->add_kword(p,table,"MEASUREMENT_SOURCE","Not specified", NULL) < 0) {
							al->free(al, sfield);
							return p->errc;
						}
					if ((i = p->find_kword(p,table,"PRINT_CONDITIONS")) < 0)	/* Create it */
						if (p->add_kword(p,table,"PRINT_CONDITIONS","Not specified", NULL) < 0) {
							al->free(al, sfield);
							return p->errc;
						}
					break;
				case tt_other:
					/* We enforce no pre-defined keywords for user defined file types */
					break;
				default:
					break;
			}
		}

		/* Output the table */

		/* First the table identifier */
		if (!t->sup_id)	/* If not suppressed */ {
			switch(t->tt) {
				case it8_7_1:
					if (fp->gprintf(fp,"IT8.7/1\n\n") < 0)
						goto write_error;
					break;
				case it8_7_2:
					if (fp->gprintf(fp,"IT8.7/2\n\n") < 0)
						goto write_error;
					break;
				case it8_7_3:
					if (fp->gprintf(fp,"IT8.7/3\n\n") < 0)
						goto write_error;
					break;
				case it8_7_4:
					if (fp->gprintf(fp,"IT8.7/4\n\n") < 0)
						goto write_error;
					break;
				case cgats_5:
					if (fp->gprintf(fp,"CGATS.5\n\n") < 0)
						goto write_error;
					break;
				case cgats_X:				/* variable CGATS type */
					if (p->cgats_type == NULL)
						goto write_error;
					if (fp->gprintf(fp,"%-7s\n\n", p->cgats_type) < 0)
						goto write_error;
					break;
				case tt_other:	/* User defined file identifier */
					if (fp->gprintf(fp,"%-7s\n\n",p->others[t->oi]) < 0)
						goto write_error;
					break;
				case tt_none:
					break;
			}
		} else {	/* At least space the next table out a bit */
			if (table == 0) {
				al->free(al, sfield);
				return err(p,-1,"cgats_write(), ID should not be suppressed on first table");
			}
			if (t->tt != p->t[table-1].tt || (t->tt == tt_other && t->oi != p->t[table-1].oi)) {
				al->free(al, sfield);
				return err(p,-1,"cgats_write(), ID should not be suppressed when table %d type is not the same as previous table",table);
			}
			if (fp->gprintf(fp,"\n\n") < 0)
				goto write_error;
		}

		/* Then all the keywords */
		for (i = 0; i < t->nkwords; i++) {
			char *qs = NULL;

			DBGF((DBGA,"CGATS writing keyword %d\n",i));

			/* Keyword and data if it is present */
			if (t->ksym[i] != NULL && t->kdata[i] != NULL) {
				if (p->emit_keywords && !standard_kword(t->ksym[i])) {	/* Do the right thing */
					if ((qs = quote_cs(al, t->ksym[i])) == NULL) {
						al->free(al, sfield);
						return err(p,-2,"quote_cs() malloc failed!");
					}
					if (fp->gprintf(fp,"KEYWORD %s\n",qs) < 0) {
						al->free(al, qs);
						goto write_error;
					}
					al->free(al, qs);
				}
	
				if ((qs = quote_cs(al, t->kdata[i])) == NULL) {
					al->free(al, sfield);
					return err(p,-2,"quote_cs() malloc failed!");
				}
				if (fp->gprintf(fp,"%s %s%s",t->ksym[i],qs,
				    t->kcom[i] == NULL ? "\n":"\t") < 0) {
					al->free(al, qs);
					goto write_error;
				}
				al->free(al, qs);
			}
			/* Comment if its present */
			if (t->kcom[i] != NULL) {
				if (fp->gprintf(fp,"# %s\n",t->kcom[i]) < 0) {
					al->free(al, qs);
					goto write_error;
				}
			}
		}

		/* Then the field specification */
		if (!t->sup_fields) {	/* If not suppressed */
			if (fp->gprintf(fp,"\n") < 0)
				goto write_error;
	
			/* Declare any non-standard fields */
			for (field = 0; field < t->nfields; field++) {
				if (p->emit_keywords && !sfield[field])	/* Non-standard */ {
					char *qs;
					if ((qs = quote_cs(al, t->fsym[field])) == NULL) {
						al->free(al, sfield);
						return err(p,-2,"quote_cs() malloc failed!");
					}
					if (fp->gprintf(fp,"KEYWORD %s\n",qs) < 0) {
						al->free(al, qs);
						goto write_error;
					}
					al->free(al, qs);
				}
			}
	
			if (fp->gprintf(fp,"NUMBER_OF_FIELDS %d\n",t->nfields) < 0)
				goto write_error;
			if (fp->gprintf(fp,"BEGIN_DATA_FORMAT\n") < 0)
				goto write_error;
			for (field = 0; field < t->nfields; field ++) {
				DBGF((DBGA,"CGATS writing field %d\n",field));
				if (fp->gprintf(fp,"%s ",t->fsym[field]) < 0)
					goto write_error;
			}
			if (fp->gprintf(fp,"\nEND_DATA_FORMAT\n") < 0)
				goto write_error;
		} else { /* Check that it is safe to suppress fields */
			cgats_table *pt = &p->t[table-1];
			if (table == 0) {
				al->free(al, sfield);
				return err(p,-1,"cgats_write(), Fields should not be suppressed on first table");
			}
			if (t->nfields != pt->nfields) {
				al->free(al, sfield);
				return err(p,-1,"cgats_write(), Fields should not be suppressed when table %d different number than previous table",table);
			}
			for (field = 0; field < t->nfields; field ++)
				if (strcmp(t->fsym[field],pt->fsym[field]) != 0
				 || t->ftype[field] != pt->ftype[field]) {
					al->free(al, sfield);
					return err(p,-1,"cgats_write(), Fields should not be suppressed when table %d types is not the same as previous table",table);
				}
		}

		/* Then the actual data */
		if (fp->gprintf(fp,"\nNUMBER_OF_SETS %d\n",t->nsets) < 0)
			goto write_error;
		if (fp->gprintf(fp,"BEGIN_DATA\n") < 0)
			goto write_error;
		for (set = 0; set < t->nsets; set++) {
			DBGF((DBGA,"CGATS writing set %d\n",set));
			for (field = 0; field < t->nfields; field++) {
				data_type tt;
				if (t->ftype[field] == r_t) {
					char fmt[30];
					double val = *((double *)t->fdata[set][field]);
					real_format(val, REAL_SIGDIG, fmt);
 					strcat(fmt," ");
					if (fp->gprintf(fp,fmt,val) < 0)
						goto write_error;
				} else if (t->ftype[field] == i_t) {
					if (fp->gprintf(fp,"%d ",*((int *)t->fdata[set][field])) < 0)
						goto write_error;
				} else if (t->ftype[field] == nqcs_t
				      && !cs_has_ws((char *)t->fdata[set][field])
				      && (sfield[field] || (tt = guess_type((char *)t->fdata[set][field]),
				                            tt != i_t && tt != r_t))) {
					/* We can only print a non-quote string if it doesn't contain white space, */
					/* quote or comment characters, and if it is a standard field or */
					/* can't be mistaken for a number. */
					if (fp->gprintf(fp,"%s ",(char *)t->fdata[set][field]) < 0)
						goto write_error;
				} else if (t->ftype[field] == nqcs_t
				      || t->ftype[field] == cs_t) {
					char *qs;
					if ((qs = quote_cs(al, (char *)t->fdata[set][field])) == NULL) {
						al->free(al, sfield);
						return err(p,-2,"quote_cs() malloc failed!");
					}
					if (fp->gprintf(fp,"%s ",qs) < 0) {
						al->free(al, qs);
						goto write_error;
					}
					al->free(al, qs);
				} else {
					al->free(al, sfield);
					return err(p,-1,"cgats_write(), illegal data type found");
				}
			}
			if (fp->gprintf(fp,"\n") < 0)
				goto write_error;
		}
		if (fp->gprintf(fp,"END_DATA\n") < 0)
			goto write_error;

		if (sfield != NULL)
			al->free(al, sfield);
		sfield = NULL;
	}
	return 0;

write_error:
	err(p,-1,"Write error to file '%s'",fp->fname(fp));
	if (sfield != NULL)
		al->free(al, sfield);
	return p->errc;
}

/* Allocate space for data with given type, and copy it from source */
/* Return NULL if alloc failed, or unknown data type */
static void *
alloc_copy_data_type(cgatsAlloc *al, data_type dtype, void *dpoint) {
	switch(dtype) {
		case r_t: {	/* Real value */
			double *p;
			if ((p = (double *)al->malloc(al, sizeof(double))) == NULL)
				return NULL;
			*p = *((double *)dpoint);
			return (void *)p;
		}
		case i_t: {	/* Integer value */
			int *p;
			if ((p = (int *)al->malloc(al, sizeof(int))) == NULL)
				return NULL;
			*p = *((int *)dpoint);
			return (void *)p;
		}
		case cs_t:	/* Character string */
		case nqcs_t: {	/* Character string */
			char *p;
			if ((p = (char *)al->malloc(al, (strlen(((char *)dpoint))+1) * sizeof(char))) == NULL)
				return NULL;
			strcpy(p, (char *)dpoint);
			return (void *)p;
		}
		case none_t:
		default:
			return NULL;
	}
	return NULL;	/* Shut the compiler up */
}

/* See if the keyword name is a standard one */
/* Return non-zero if it is standard */
static int
standard_kword(const char *ksym) {
	if (ksym == NULL)
		return 0;
	if (strcmp(ksym,"ORIGINATOR") == 0)
		return 1;
	if (strcmp(ksym,"DESCRIPTOR") == 0)
		return 1;
	if (strcmp(ksym,"CREATED") == 0)
		return 1;
	if (strcmp(ksym,"MANUFACTURER") == 0)
		return 1;
	if (strcmp(ksym,"PROD_DATE") == 0)
		return 1;
	if (strcmp(ksym,"SERIAL") == 0)
		return 1;
	if (strcmp(ksym,"MATERIAL") == 0)
		return 1;
	if (strcmp(ksym,"INSTRUMENTATION") == 0)
		return 1;
	if (strcmp(ksym,"MEASUREMENT_SOURCE") == 0)
		return 1;
	if (strcmp(ksym,"PRINT_CONDITIONS") == 0)
		return 1;
	return 0;
}

/* See if the keyword name is reserved. */
/* (code generates it automatically) */
/* Return non-zero if it is reserved */
static int
reserved_kword(const char *ksym) {
	if (ksym == NULL)
		return 0;
	if (strcmp(ksym,"NUMBER_OF_FIELDS") == 0)
		return 1;
	if (strcmp(ksym,"BEGIN_DATA_FORMAT") == 0)
		return 1;
	if (strcmp(ksym,"END_DATA_FORMAT") == 0)
		return 1;
	if (strcmp(ksym,"NUMBER_OF_SETS") == 0)
		return 1;
	if (strcmp(ksym,"BEGIN_DATA") == 0)
		return 1;
	if (strcmp(ksym,"END_DATA") == 0)
		return 1;
	if (strcmp(ksym,"KEYWORD") == 0)
		return 1;
	return 0;
}

/* See if the field name is a standard one */
/* with an expected data type */
static data_type
standard_field(const char *fsym) {
	if (strcmp(fsym,"SAMPLE_ID") == 0)
		return nqcs_t;
	if (strcmp(fsym,"STRING") == 0)
		return cs_t;
	if (strncmp(fsym,"CMYK_",5) == 0) {
		if (fsym[5] == 'C')
			return r_t;
		if (fsym[5] == 'M')
			return r_t;
		if (fsym[5] == 'Y')
			return r_t;
		if (fsym[5] == 'K')
			return r_t;
		return none_t;
	}
	/* Non standard, but logical */
	if (strncmp(fsym,"CMY_",4) == 0) {
		if (fsym[4] == 'C')
			return r_t;
		if (fsym[4] == 'M')
			return r_t;
		if (fsym[4] == 'Y')
			return r_t;
		return none_t;
	}
	if (strncmp(fsym,"D_",2) == 0) {
		if (strcmp(fsym + 2, "RED") == 0)
			return r_t;
		if (strcmp(fsym + 2, "GREEN") == 0)
			return r_t;
		if (strcmp(fsym + 2, "BLUE") == 0)
			return r_t;
		if (strcmp(fsym + 2, "VIS") == 0)
			return r_t;
		return none_t;
	}
	if (strncmp(fsym,"RGB_",4) == 0) {
		if (fsym[4] == 'R')
			return r_t;
		if (fsym[4] == 'G')
			return r_t;
		if (fsym[4] == 'B')
			return r_t;
		return none_t;
	}
	if (strncmp(fsym,"SPECTRAL_",9) == 0) {
		if (strcmp(fsym + 9, "NM") == 0)
			return r_t;
		if (strcmp(fsym + 9, "PCT") == 0)
			return r_t;
		return none_t;
	}
	if (strncmp(fsym,"XYZ_",4) == 0) {
		if (fsym[4] == 'X')
			return r_t;
		if (fsym[4] == 'Y')
			return r_t;
		if (fsym[4] == 'Z')
			return r_t;
		return none_t;
	}
	if (strncmp(fsym,"XYY_",4) == 0) {
		if (fsym[4] == 'X')
			return r_t;
		if (fsym[4] == 'Y')
			return r_t;
		if (strcmp(fsym + 4, "CAPY") == 0)
			return r_t;
		return none_t;
	}
	if (strncmp(fsym,"LAB_",4) == 0) {
		if (fsym[4] == 'L')
			return r_t;
		if (fsym[4] == 'A')
			return r_t;
		if (fsym[4] == 'B')
			return r_t;
		if (fsym[4] == 'C')
			return r_t;
		if (fsym[4] == 'H')
			return r_t;
		if (strcmp(fsym + 4, "DE") == 0)
			return r_t;
		return none_t;
	}
	if (strncmp(fsym,"STDEV_",6) == 0) {
		if (fsym[6] == 'X')
			return r_t;
		if (fsym[6] == 'Y')
			return r_t;
		if (fsym[6] == 'Z')
			return r_t;
		if (fsym[6] == 'L')
			return r_t;
		if (fsym[6] == 'A')
			return r_t;
		if (fsym[6] == 'B')
			return r_t;
		if (strcmp(fsym + 6, "DE") == 0)
			return r_t;
		return none_t;
	}
	return none_t;
}

/* Return non-zero if char string has an embedded */
/* white space, quote or comment character. */
static int
cs_has_ws(const char *cs)
	{
	int i;
	for (i = 0; cs[i] != '\000'; i++)
		{
		switch (cs[i])
			{
			case ' ':
			case '\r':
			case '\n':
			case '\t':
			case '"':
			case '#':
				return 1;
			}
		}
	return 0;
	}

/* Return a string with quotes added */
/* Return NULL if malloc failed */
/* (Returned string should be free()'d after use) */
static char *
quote_cs(cgatsAlloc *al, const char *cs) {
	int i,j;
	char *rs;
	/* see how much space we need for returned string */
	for (i = 0, j = 3; cs[i] != '\000'; i++, j++)
		if (cs[i] == '"')
			j++;
	if ((rs = (char *)al->malloc(al, j * sizeof(char))) == NULL) {
		return NULL;
	}

	j = 0;
	rs[j++] = '"';
	for (i = 0; cs[i] != '\000'; i++, j++) {
		if (cs[i] == '"')
			rs[j++] = '"';
		rs[j] = cs[i];
	}
	rs[j++] = '"';
	rs[j] = '\000';
	return rs;
}

/* Remove quotes from the string */
static void
unquote_cs(char *cs) {
	int sl,i,j;
	int s;		/* flag - set if skipped last character */

	sl = strlen(cs);

	if (sl < 2 || cs[0] != '"' || cs[sl-1] != '"')
		return;

	for (i = 1, j = 0, s = 1; i < (sl-1); i++) {
		if (s == 1 || cs[i-1] != '"' || cs[i] != '"') {
			cs[j++] = cs[i];	/* skip second " in a row */
			s = 0;
		} else
			s = 1;
	}
	cs[j] = '\000';

	return;
}

/* Guess the data type from the string */
static data_type
guess_type(const char *cs)
	{
	int i;
	int rf = 0;

	/* See if its a quoted string */
	if (cs[0] == '"')
		{
		for (i = 1;cs[i] != '\000';i++);
		if (i >= 2 && cs[i-1] == '"')
			return cs_t;
		return nqcs_t;
		}
	/* See if its an integer or real */
	for (i = 0;;i++)
		{
		char c = cs[i];
		if (c == '\000')
			break;
		if (c >= '0' && c <= '9')	/* Strictly numeric */
			continue;
		if (c == '-' &&		/* Allow appropriate '-' */
			( i == 0 ||
			 ( i > 0 && (cs[i-1] == 'e' || cs[i-1] == 'E'))))
			continue;
		if (c == '+' &&		/* Allow appropriate '+' */
			( i == 0 ||
			 ( i > 0 && (cs[i-1] == 'e' || cs[i-1] == 'E'))))
			continue;
		if (!(rf & 3) && c == '.')	/* Allow one '.' before 'e' in real */
			{
			rf |= 1;
			continue;
			}
		if (!(rf & 2) && (c == 'e' || c == 'E'))	/* Allow one 'e' in real */
			{
			rf |= 2;
			continue;
			}
		/* Not numeric or incorrect 'e' or '.' or '-' or '+' */
		return nqcs_t;
		}
	if (rf)
		return r_t;
	return i_t;
	}

/* Set the character format to the appropriate printf() */
/* format given the real value and the desired number of significant digits. */
/* We try to do this while not using the %e format for normal values. */
/* The fmt string space is assumed to be big enough to contain the format */
static void
real_format(double value, int nsd, char *fmt) {
	int ndigs;
	int tot = nsd + 1;
	int xtot = tot;
	if (value == 0.0) {
		sprintf(fmt,"%%%d.%df",tot,tot-2);
		return;
	}
	if (value != value) {		/* Hmm. A nan */
		sprintf(fmt,"%%f");
		return;
	}
	if (value < 0.0) {
		value = -value;
		xtot++;
	}
	if (value < 1.0) {
		int thr = -5;
		ndigs = (int)(log10(value));
		if (ndigs <= thr) {
			sprintf(fmt,"%%%d.%de",xtot,tot-2);
			return;
		}
		sprintf(fmt,"%%%d.%df",xtot-ndigs,nsd-ndigs);
		return;
	} else {
		int thr = -0;
		ndigs = (int)(log10(value));
		if (ndigs >= (nsd + thr)) {
			sprintf(fmt,"%%%d.%de",xtot,tot-2);
			return;
		}
		sprintf(fmt,"%%%d.%df",xtot,(nsd + thr)-ndigs);
		return;
	}
}

/* ---------------------------------------------------------- */
/* Debug and test code */
/* ---------------------------------------------------------- */

#ifdef STANDALONE_TEST

/* Dump the contents of a cgats structure to the given output */
static void
cgats_dump(cgats *p, cgatsFile *fp) {
	int tn;

	fp->gprintf(fp,"Number of tables = %d\n",p->ntables);
	for (tn = 0; tn < p->ntables; tn++) {
		cgats_table *t;
		int i,j;
		t = &p->t[tn];

	
		fp->gprintf(fp,"\nTable %d:\n",tn);

		switch(t->tt)	/* Table identifier */
			{
			case it8_7_1:
				fp->gprintf(fp,"Identifier = 'IT8.7/1'\n");
				break;
			case it8_7_2:
				fp->gprintf(fp,"Identifier = 'IT8.7/2'\n");
				break;
			case it8_7_3:
				fp->gprintf(fp,"Identifier = 'IT8.7/3'\n");
				break;
			case it8_7_4:
				fp->gprintf(fp,"Identifier = 'IT8.7/4'\n");
				break;
			case cgats_5:
				fp->gprintf(fp,"Identifier = 'CGATS.5'\n");
				break;
			case cgats_X:
				fp->gprintf(fp,"Identifier = '%s'\n",p->cgats_type);
				break;
			case tt_other:	/* User defined file identifier */
				fp->gprintf(fp,"Identifier = '%s'\n",p->others[t->oi]);
				break;
			default:
				fp->gprintf(fp,"**ILLEGAL**\n");
				break;
			}

		fp->gprintf(fp,"\nNumber of keywords = %d\n",t->nkwords);
		
		/* Dump all the keyword symbols and values */
		for (i = 0; i < t->nkwords; i++) {
			if (t->ksym[i] != NULL && t->kdata[i] != NULL)
				{
				if (t->kcom[i] != NULL)
					fp->gprintf(fp,"Keyword '%s' has value '%s' and comment '%s'\n",
					        t->ksym[i],t->kdata[i],t->kcom[i]);
				else
					fp->gprintf(fp,"Keyword '%s' has value '%s'\n",t->ksym[i],t->kdata[i]);
				}
			if (t->kcom[i] != NULL)
				fp->gprintf(fp,"Comment '%s'\n",t->kcom[i]);
		}
			
		fp->gprintf(fp,"\nNumber of field defs = %d\n",t->nfields);

		/* Dump all the field symbols */
		for (i = 0; i < t->nfields; i++) {		
			char *fname;
			switch(t->ftype[i]) {
				case r_t:
					fname = "real";
					break;
				case i_t:
					fname = "integer";
					break;
				case cs_t:
					fname = "character string";
					break;
				case nqcs_t:
					fname = "non-quoted char string";
					break;
				default:
					fname = "illegal";
					break;
			}
			fp->gprintf(fp,"Field '%s' has type '%s'\n",t->fsym[i],fname);
		}

		fp->gprintf(fp,"\nNumber of sets = %d\n",t->nsets);

		/* Dump all the set values */
		for (j = 0; j < t->nsets; j++) {
			for (i = 0; i < t->nfields; i++) {
				switch(t->ftype[i]) {
					case r_t: {
						char fmt[30];
						double val = *((double *)t->fdata[j][i]);
						fmt[0] = ' ';
						real_format(val, REAL_SIGDIG, fmt+1);
						fp->gprintf(fp,fmt,*((double *)t->fdata[j][i]));
						break;
					}
					case i_t:
						fp->gprintf(fp," %d",*((int *)t->fdata[j][i]));
						break;
					case cs_t:
					case nqcs_t:
						fp->gprintf(fp," %s",((char *)t->fdata[j][i]));
						break;
					default:
						fp->gprintf(fp," illegal");
						break;
				}
			}
			fp->gprintf(fp,"\n");
		}
	}
}

int
main(int argc, char *argv[]) {
	char *fn;
	cgatsFile *fp;
	cgats *pp;

	if (argc == 1) {
		/* Write test */
		pp = new_cgats();	/* Create a CGATS structure */
	
		if (pp->add_table(pp, cgats_5, 0) < 0	/* Start the first table */
		 || pp->add_kword(pp, 0, NULL, NULL, "Comment only test comment") < 0
		 || pp->add_kword(pp, 0, "TEST_KEY_WORD", "try this out", "Keyword comment") < 0
		 || pp->add_kword(pp, 0, "TEST_KEY_WORD2", "try this\" out \"\" huh !", NULL) < 0
	
		 || pp->add_field(pp, 0, "SAMPLE_ID", cs_t) < 0
		 || pp->add_field(pp, 0, "SAMPLE_LOC", nqcs_t) < 0
		 || pp->add_field(pp, 0, "XYZ_X", r_t) < 0)
			error("Initial error: '%s'",pp->err);
	
		if (pp->add_set(pp, 0, "1", "A1",  0.000000012345678) < 0
		 || pp->add_set(pp, 0, "2", "A1",  0.00000012345678) < 0
		 || pp->add_set(pp, 0, "3", "A1",  0.0000012345678) < 0
		 || pp->add_set(pp, 0, "4", "A1",  0.000012345678) < 0
		 || pp->add_set(pp, 0, "5", "A1",  0.000012345678) < 0
		 || pp->add_set(pp, 0, "6", "A2",  0.00012345678) < 0
		 || pp->add_set(pp, 0, "7", "A5",0.0012345678) < 0
		 || pp->add_set(pp, 0, "8", "A5",  0.012345678) < 0
		 || pp->add_set(pp, 0, "9", "A5",  0.12345678) < 0
		 || pp->add_set(pp, 0, "10", "A5",  0.00000000) < 0
		 || pp->add_set(pp, 0, "11", "A5",  1.2345678) < 0
		 || pp->add_set(pp, 0, "12", "A5",  12.345678) < 0
		 || pp->add_set(pp, 0, "13", "A5",  123.45678) < 0
		 || pp->add_set(pp, 0, "14", "A5",  1234.5678) < 0
		 || pp->add_set(pp, 0, "15", "A5", 12345.678) < 0
		 || pp->add_set(pp, 0, "16", "A5", 123456.78) < 0
		 || pp->add_set(pp, 0, "17", "A5", 1234567.8) < 0
		 || pp->add_set(pp, 0, "18", "A5", 12345678.0) < 0
		 || pp->add_set(pp, 0, "19", "A5", 123456780.0) < 0
		 || pp->add_set(pp, 0, "20", "A5", 1234567800.0) < 0
		 || pp->add_set(pp, 0, "21", "A5", 12345678000.0) < 0
		 || pp->add_set(pp, 0, "22", "A5", 123456780000.0) < 0
		 || pp->add_set(pp, 0, "23", "A5", 1234567800000.0) < 0)
			error("Adding set error '%s'",pp->err);
		
		if (pp->add_table(pp, cgats_5, 0) < 0			/* Start the second table */
		 || pp->set_table_flags(pp, 1, 1, 1, 1) < 0		/* Suppress id, kwords and fields */
		 || pp->add_kword(pp, 1, NULL, NULL, "Second Comment only test comment") < 0)
			error("Adding table error '%s'",pp->err);
	
		if (pp->add_field(pp, 1, "SAMPLE_ID", cs_t) < 0	/* Need to define fields same as table 0 */
		 || pp->add_field(pp, 1, "SAMPLE_LOC", nqcs_t) < 0
		 || pp->add_field(pp, 1, "XYZ_X", r_t) < 0)
			error("Adding field error '%s'",pp->err);
	
		if (pp->add_set(pp, 1, "4", "A4",  -0.000012345678) < 0
		 || pp->add_set(pp, 1, "5", "A5",  -0.00012345678) < 0
		 || pp->add_set(pp, 1, "6", "A5",  -0.0012345678) < 0
		 || pp->add_set(pp, 1, "7", "A5",  -0.012345678) < 0
		 || pp->add_set(pp, 1, "8", "A5",  -0.12345678) < 0
		 || pp->add_set(pp, 1, "9", "A5",  -1.2345678) < 0
		 || pp->add_set(pp, 1, "10", "A5", -12.345678) < 0
		 || pp->add_set(pp, 1, "11", "A5", -123.45678) < 0
		 || pp->add_set(pp, 1, "12", "A5", -1234.5678) < 0
		 || pp->add_set(pp, 1, "13", "A5", -12345.678) < 0
		 || pp->add_set(pp, 1, "14", "A5", -123456.78) < 0
		 || pp->add_set(pp, 1, "15", "A5", -1234567.8) < 0
		 || pp->add_set(pp, 1, "16", "A5", -12345678.0) < 0
		 || pp->add_set(pp, 1, "17", "A5", -123456780.0) < 0
		 || pp->add_set(pp, 1, "18", "A5", -1234567800.0) < 0
		 || pp->add_set(pp, 1, "19", "A5", -12345678000.0) < 0
		 || pp->add_set(pp, 1, "20", "A5", -123456780000.0) < 0)
			error("Adding set 2 error '%s'",pp->err);
	
		if ((fp = new_cgatsFileStd_name("fred.it8", "w")) == NULL)
			error("Error opening '%s' for writing","fred.it8");
	
		if (pp->write(pp, fp))
			error("Write error : %s",pp->err);
	
		fp->del(fp);		/* Close file */
		pp->del(pp);		/* Clean up */
	}

	/* Read test */
	pp = new_cgats();	/* Create a CGATS structure */

	/* Setup to cope with Argyll files */
	if (pp->add_other(pp, "CTI1") == -2
	 || pp->add_other(pp, "CTI2") == -2
	 || pp->add_other(pp, "CTI3") == -2)
		error("Adding other error '%s'",pp->err); 

	/* Setup to cope with colorblind files */
	if (pp->add_other(pp, "CBSC") == -2
	 || pp->add_other(pp, "CBTA") == -2
	 || pp->add_other(pp, "CBPR") == -2)
		error("Adding other 2 error '%s'",pp->err); 

	if (argc == 2)
		fn = argv[1];
	else
		fn = "fred.it8";

	if ((fp = new_cgatsFileStd_name(fn, "r")) == NULL)
		error("Error opening '%s' for reading",fn);
	if (pp->read(pp, fp))
		error("Read error : %s",pp->err);
	fp->del(fp);		/* Close file */

	if ((fp = new_cgatsFileStd_fp(stdout)) == NULL)
		error("Error opening stdout");
	cgats_dump(pp, fp);
	fp->del(fp);		/* delete file object */

	pp->del(pp);		/* Clean up */
	return 0;
}


/* Basic printf type error() and warning() routines */

void
error(const char *fmt, ...) {
	va_list args;

	fprintf(stderr,"cgats: Error - ");
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
	fprintf(stderr, "\n");
	exit (-1);
}

void
warning(const char *fmt, ...) {
	va_list args;

	fprintf(stderr,"cgats: Warning - ");
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
	fprintf(stderr, "\n");
}

#endif /* STANDALONE_TEST */
/* ---------------------------------------------------------- */
