Files
2026-08-20 20:28:40 +01:00

2280 lines
64 KiB
C

/*
* 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_type(cgats *p, int table, 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 del_table(cgats *p, int table);
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, 1, sizeof(cgats))) == 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_type = set_table_type;
p->set_table_flags = set_table_flags;
p->del_table = del_table;
p->set_cgats_type = set_cgats_type;
p->add_other = add_other;
p->get_oi = get_oi;
p->add_kword = add_kword;
p->add_kword_at = add_kword_at;
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->e.c = errc;
va_start(args, fmt);
vsprintf(p->e.m, fmt, args);
va_end(args);
/* If this is the first registered error */
if (p->fe.c != 0) {
p->fe.c = p->e.c;
strcpy(p->fe.m, p->e.m);
}
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->fe.c != 0) {
if (mes != NULL)
*mes = p->fe.m;
return p->fe.c;
}
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->e.c = 0;
p->e.m[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->e.c = 0;
p->e.m[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->e.c and p->e.m 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->e.c = 0;
p->e.m[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->e.c != 0) { /* get_token got an error */
err(p, -1, "%s", pp->e.m);
pp->del(pp);
DBGF((DBGA,"Get token got error '%s'\n",pp->e.m));
return p->e.c;
}
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->e.m);
pp->del(pp);
DBGF((DBGA,"Read line got error '%s'\n",pp->e.m));
return p->e.c;
}
}
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->e.c;
}
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->calloc(p->al,
(strlen(tp)+1), sizeof(char))) == NULL) {
err(p,-1,"Failed to malloc space for CGATS.X keyword");
pp->del(pp);
return p->e.c;
}
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->e.c;
}
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->e.c;
}
/* 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->e.c;
}
} 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->e.c;
}
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->e.c;
}
}
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->e.c;
}
}
}
/* 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->e.c;
}
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->e.c;
}
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->e.c;
}
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->e.c;
}
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->e.c;
}
}
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->e.c;
}
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->e.c;
}
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->e.c;
}
#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->e.c;
}
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->e.c;
}
/* 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->e.c;
}
/* 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->e.c;
}
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->e.c = -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->e.c = -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->e.c;
}
/* 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->e.c;
}
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->e.c = 0;
p->e.m[0] = '\000';
if (p->cgats_type != NULL)
al->free(al, p->cgats_type);
if ((p->cgats_type = (char *)al->calloc(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. */
/* Returns existing if already registered. */
/* Return -2 & set errc and err on system error */
static int
add_other(cgats *p, const char *osym) {
int oi;
cgatsAlloc *al = p->al;
p->e.c = 0;
p->e.m[0] = '\000';
if ((oi = get_oi(p, osym)) >= 0)
return oi;
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->calloc(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->e.c = 0;
p->e.m[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->e.c = 0;
p->e.m[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;
}
/* Override the table type */
static int
set_table_type(cgats *p, int table, table_type tt, int oi) {
cgats_table *t = &p->t[table];
t->tt = tt;
t->oi = oi;
return 0;
}
/* 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->e.c = 0;
p->e.m[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;
}
/* delete a table */
/* Note that any following tables will be shuffled down. */
static int del_table(cgats *p, int table) {
cgats_table *t;
p->e.c = 0;
p->e.m[0] = '\000';
if (table < 0 || table >= p->ntables)
return err(p,-1, "cgats.del_table(), table number '%d' is out of range",table);
cgats_table_free(&p->t[table]);
while ((table+1) < p->ntables) {
p->t[table] = p->t[table+1];
table++;
}
p->ntables--;
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->e.c = 0;
p->e.m[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->e.c = 0;
p->e.m[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->e.c = 0;
p->e.m[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->e.c = 0;
p->e.m[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->e.c = 0;
p->e.m[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->calloc(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->e.c = 0;
p->e.m[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->calloc(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->e.c = 0;
p->e.m[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->e.c = 0;
p->e.m[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->calloc(al, t->nfields, sizeof(void *))) == NULL)
return err(p,-2,"cgats.add_item(), malloc failed!");
if ((t->fdata[t->nsets-1] = (void **)al->calloc(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->e.c = 0;
p->e.m[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->calloc(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->e.c;
}
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->e.c;
}
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->e.c;
}
}
/* 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->e.c;
}
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->e.c;
}
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->e.c;
}
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->e.c;
}
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->e.c;
}
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->e.c;
}
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->e.c;
}
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->e.c;
}
/* 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->calloc(al, 1, sizeof(double))) == NULL)
return NULL;
*p = *((double *)dpoint);
return (void *)p;
}
case i_t: { /* Integer value */
int *p;
if ((p = (int *)al->calloc(al, 1, 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->calloc(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->calloc(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 > 310 || ndigs < -310) { /* Protect against silliness.. */
strcpy(fmt,"%g");
return;
}
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 > 310 || ndigs < -310) { /* Protect against silliness.. */
strcpy(fmt,"%g");
return;
}
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->e.m);
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->e.m);
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->e.m);
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->e.m);
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->e.m);
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->e.m);
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->e.m);
/* 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->e.m);
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->e.m);
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 */
/* ---------------------------------------------------------- */