From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- cgats/cgats.c | 2166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2166 insertions(+) create mode 100644 cgats/cgats.c (limited to 'cgats/cgats.c') diff --git a/cgats/cgats.c b/cgats/cgats.c new file mode 100644 index 0000000..baf9992 --- /dev/null +++ b/cgats/cgats.c @@ -0,0 +1,2166 @@ + +/* + * 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 License.txt file in this directory for licensing details. + */ + +#define _CGATS_C_ /* Turn on implimentation code */ + +#include +#include +#include + +#undef DEBUG /* Debug only in slected places */ + +#ifdef DEBUG +# define DBGA g_log, 0 /* First argument to DBGF() */ +# define DBGF(xx) a1logd xx +#else +# define DBGF(xx) +#endif + +#ifdef STANDALONE_TEST +extern void error(const char *fmt, ...), warning(const char *fmt, ...); +#endif + +#include +#include +#include +#include +#include "pars.h" +#include "cgats.h" + +#define REAL_SIGDIG 5 /* 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 + + 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 */ + + 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 */ + bt = ty; + } + } + /* 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 */ + 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 4 */ + t->nfieldsa += 4; + 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 (!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 (!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) { + ndigs = (int)(log10(value)); + if (ndigs <= -2) { + sprintf(fmt,"%%%d.%de",xtot,tot-2); + return; + } + sprintf(fmt,"%%%d.%df",xtot-ndigs,nsd-ndigs); + return; + } else { + ndigs = (int)(log10(value)); + if (ndigs >= (nsd -1)) + { + sprintf(fmt,"%%%d.%de",xtot,tot-2); + return; + } + sprintf(fmt,"%%%d.%df",xtot,(nsd-1)-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.000012345678) < 0 + || pp->add_set(pp, 0, "2", "A2", 0.00012345678) < 0 + || pp->add_set(pp, 0, "3 ", "A#5",0.0012345678) < 0 + || pp->add_set(pp, 0, "4", "A5", 0.012345678) < 0 + || pp->add_set(pp, 0, "5", "A5", 0.12345678) < 0 + || pp->add_set(pp, 0, "5", "A5", 0.00000000) < 0 + || pp->add_set(pp, 0, "6", "A5", 1.2345678) < 0 + || pp->add_set(pp, 0, "7", "A5", 12.345678) < 0 + || pp->add_set(pp, 0, "8", "A5", 123.45678) < 0 + || pp->add_set(pp, 0, "9", "A5", 1234.5678) < 0 + || pp->add_set(pp, 0, "10", "A5", 12345.678) < 0 + || pp->add_set(pp, 0, "12", "A5", 123456.78) < 0 + || pp->add_set(pp, 0, "13", "A5", 1234567.8) < 0 + || pp->add_set(pp, 0, "14", "A5", 12345678.0) < 0 + || pp->add_set(pp, 0, "15", "A5", 123456780.0) < 0 + || pp->add_set(pp, 0, "16", "A5", 1234567800.0) < 0 + || pp->add_set(pp, 0, "17", "A5", 12345678000.0) < 0 + || pp->add_set(pp, 0, "18", "A5", 123456780000.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 */ +/* ---------------------------------------------------------- */ -- cgit v1.2.3