
/* 
 * Argyll Color Correction System
 * Named Color Access Library
 *
 * Author: Graeme W. Gill
 * Date:   3/12/2013
 *
 * Copyright 2013 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 */

/*
 * Currently supports CxF2, CxF3 & ICC named color profiles.
 */

/*
 * TTBD:
 *
 * Probably want to add some other formats:
 * (see <http://www.selapa.net/swatchbooker/>
 *  and <http://www.selapa.net/swatches/colors/fileformats.php>  )
 *
 * 	Adobe .aco
 * 	      .acb
 * 	      .acbl
 * 	      .ase
 * 	      .bcf
 * corel  .cpl
 * corel  .xml
 * autocad .acb			RGB only 
 *
 * and lower quality RGB formats:
 *
 * Adobe  .acf		?? May support Lab
 * corel  .acb
 *
 * Paint Shop Pro .pal ???
 *
 * RAL ?
 */

#undef DEBUG

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include <errno.h>
#include "copyright.h"
#include "aconfig.h"
#ifndef SALONEINSTLIB
#include "numlib.h"
#include "icc.h"
#else
#include "numsup.h"
#endif
#include "conv.h"
#include "cgats.h"
#include "xspect.h"
#include "mxml.h"
#include "namedc.h"
#ifdef STANDALONE_TEST
#include "ui.h"
#endif /* STANDALONE_TEST */

#ifndef STANDALONE_TEST

# define DEB6 6
# define DEB4 4

#ifdef NT       /* You'd think there might be some standards.... */
# ifndef __BORLANDC__
#  define stricmp _stricmp
# endif
#else
# define stricmp strcasecmp
#endif

/* Forward declarations */
static void clear_namedc(namedc *p);
static void clear_nce(nce *p);

static ORD32 do_hash(ORD32 hash, const char *s) {
	int i;
    for (i = 0; s[i] != '\000'; i++) {
        hash += s[i];
        hash += (hash << 10);
        hash ^= (hash >> 6);
    }
    return hash;
}

static int do_hash2(const char *s1, const char *s2) {
	ORD32 hash;

	hash = do_hash(0, s1);
	hash = do_hash(hash, s2);

	return (int)hash;
}

/* Compare while ignoring possible prefix on s1 */
static int pfxcmp(const char *s1, const char *s2) {
	const char *cp;

	if ((cp = strchr(s1, ':')) == NULL)
		cp = s1;
	else
		cp++;

	return strcmp(cp, s2);
}

/* XML data type callback for mxmlLoadFile() */
static mxml_type_t
type_cb(mxml_node_t *node) {
	const char *name = node->value.element.name;
	mxml_node_t *parent = mxmlGetParent(node);
	const char *pname = NULL;

	if (parent != NULL)
		pname = parent->value.element.name;

//	printf("~1 type_cb got node named '%s', parent '%s'\n",name,pname);

	if (pname == NULL)
		return MXML_TEXT;

	if (pfxcmp(pname, "ColorValues") == 0
	 && pfxcmp(name, "ReflectanceSpectrum") == 0)
		return MXML_REAL;

	if ((pfxcmp(pname, "ColorCIELab") == 0
	  || pfxcmp(pname, "ColorSpaceCIELab") == 0)
	 && (pfxcmp(name, "L") == 0
	  || pfxcmp(name, "A") == 0
	  || pfxcmp(name, "B") == 0))
		return MXML_REAL;

	if ((pfxcmp(pname, "ColorCIEXYZ") == 0
	  || pfxcmp(pname, "ColorSpaceCIEXYZ") == 0)
	 && (pfxcmp(name, "X") == 0
	  || pfxcmp(name, "Y") == 0
	  || pfxcmp(name, "Z") == 0))
		return MXML_REAL;

	if ((pfxcmp(pname, "ColorSRGB") == 0
	  || pfxcmp(pname, "ColorSpaceSRGB") == 0)
	 && (pfxcmp(name, "R") == 0
	  || pfxcmp(name, "G") == 0
	  || pfxcmp(name, "B") == 0))
		return MXML_REAL;

	if ((pfxcmp(pname, "ColorCMYK") == 0
	  || pfxcmp(pname, "ColorSpaceCMYK") == 0)
	 && (pfxcmp(name, "Cyan") == 0
	  || pfxcmp(name, "Magenta") == 0
	  || pfxcmp(name, "Yellow") == 0
	  || pfxcmp(name, "Black") == 0))
		return MXML_REAL;

	if ((pfxcmp(pname, "FileInformation") == 0
	  || pfxcmp(pname, "Header") == 0)
	 && (pfxcmp(name, "Creator") == 0
	  || pfxcmp(name, "CreationDate") == 0
	  || pfxcmp(name, "Description") == 0))
		return MXML_OPAQUE;		/* Don't split strings up */

	if ((pfxcmp(pname, "IlluminationOptions") == 0
	  || pfxcmp(pname, "TristimulusSpec") == 0
	  || pfxcmp(pname, "ColorSpaceSpecificationSpectrumTristimulus") == 0)
	 && (pfxcmp(name, "Illuminant") == 0
	  || pfxcmp(name, "Observer") == 0
	  || pfxcmp(name, "FieldOfView") == 0))
		return MXML_OPAQUE;		/* Don't split strings up */

	return MXML_TEXT;
}


static void sax_cb(mxml_node_t *node, mxml_sax_event_t event, void *data) {
	namedc *p = (namedc *)data;

	if (event == MXML_SAX_ELEMENT_OPEN) {
		const char *pname = mxmlGetElement(node);

		if (pfxcmp(pname, "Resources") == 0) {
//printf("~1 open of Resources\n");
			p->indata = 1;
		}

		if ((p->options & NAMEDC_OP_NODATA) == 0
		 || !p->indata) {
//printf("~1 options 0x%x, indata %d, retaining '%s'\n",p->options,p->indata,pname);
a1logd(p->log, 4, "sax_cb: retaining %s\n",pname);
			mxmlRetain(node);
		}

	} else if (event == MXML_SAX_DIRECTIVE) {
		mxmlRetain(node);
	} else if (event == MXML_SAX_DATA) {
		/* If the parent was retained, then retain this data node as well. */
		if (mxmlGetRefCount(mxmlGetParent(node)) > 1) {
			mxmlRetain(node);
		}
	} else if (event == MXML_SAX_ELEMENT_CLOSE) {
		const char *pname = mxmlGetElement(node);
		if(pfxcmp(pname, "Resources") == 0) {
//printf("~1 close of Resources\n");
			p->indata = 0;
		}
	}
}


/* Return a temporary key with prefix */
static char *pfx(namedc *p, char *key) {
	if (p->pfx[0] == '\000')
		return key;

	snprintf(p->prefix, NAMEDC_PLEN, "%s:%s", p->pfx, key);

	return p->prefix;
}

/* Return a temporary key with prefix at front and after each '/' */
static char *pfxp(namedc *p, char *path) {
	char *cp = p->prefix;
	char *sp, *ep;
	int plen, slen;

	if (p->pfx[0] == '\000')
		return path;

	plen = strlen(p->pfx);

	/* Figure out the first source key */
	sp = path;
	if ((ep = strchr(sp, '/')) != NULL) {
		slen = ep - sp;
		ep++;				/* Point beyond '/' */
	} else {
		slen = strlen(sp);
		ep = sp + slen;		/* point at '\000' */
	}

	for (;;) {
		if ((NAMEDC_PLEN -1 -(cp - p->prefix)) < (plen + 1 + slen))
			break;			/* No room for prefix + path element */

		strcpy(cp, p->pfx);
		cp += plen;
		*cp++ = ':';
		strncpy(cp, sp, slen);
		cp += slen;
		
		/* Figure out the next source key */
		sp = ep;
		
		if ((ep = strchr(sp, '/')) != NULL) {
			slen = ep - sp;
			ep++;				/* Point beyond '/' */
		} else {
			slen = strlen(sp);
			ep = sp + slen;		/* point at '\000' */
		}

		if (slen <= 0)			/* No more path elements */
			break;

		if ((NAMEDC_PLEN -1 -(cp - p->prefix)) < 1)
			break;			/* No room for '/' */
		*cp++ = '/';
	}
	*cp++ = '\000';

	return p->prefix;
}

/* Return a temporary key with suffix */
static char *sfx(namedc *p, char *key) {
	if (p->pfx[0] == '\000')
		return key;

	snprintf(p->prefix, 200, "%s:%s", key, p->pfx);

	return p->prefix;
}

/* Read in a namedc from a cxf2 or cxf3 file */
/* Return nz on error */
static int read_cxf(namedc *p, const char *filename, int options) {
    FILE *fp;
	char *pfilename;
    mxml_node_t *tree, *cxf, *pnode, *node;
	const char *attr, *name;
	int cxf2 = 0;
	int specmin = 0, specinc = 0;
	int i, j;

	a1logd(p->log, 1, "read_cxf: file '%s' options 0x%x\n",filename,options);
	
	if (p->filename == NULL) {
		if ((pfilename = strdup(filename)) == NULL) {
			snprintf(p->err, NAMEDC_ERRL, "Malloc of filename failed");
			a1logd(p->log, 1, "read_cxf: %s\n",p->err);
			return p->errc = 2;
		}
		clear_namedc(p);
		p->filename = pfilename;
	}
	p->options = options;

    if ((fp = fopen(p->filename, "r")) == NULL) {
		snprintf(p->err, NAMEDC_ERRL, "Opening XML file '%s' failed with %s",
		                                     p->filename,strerror(errno));
		a1logd(p->log, 1, "read_cxf: %s\n",p->err);
		return p->errc = 1;
	}

#ifdef NEVER
    tree = mxmlLoadFile(NULL, fp, type_cb);
#else
	p->indata = 0;
	tree = mxmlSAXLoadFd(NULL, fileno(fp), type_cb, sax_cb, (void *)p);
#endif
    fclose(fp);

	if (tree == NULL) {
		snprintf(p->err, NAMEDC_ERRL, "Parsing XML file '%s' failed",p->filename);
		a1logd(p->log, 1, "read_cxf: %s\n",p->err);
		return p->errc = 1;
	}

	if ((cxf = mxmlFindElement(tree, tree, NULL, NULL, NULL, MXML_DESCEND_FIRST)) == NULL
	 || mxmlGetType(cxf) != MXML_ELEMENT) {
		snprintf(p->err, NAMEDC_ERRL, "Failed to find top element in '%s'",p->filename);
		a1logd(p->log, 1, "read_cxf: %s\n",p->err);
		mxmlDelete(tree);
		return p->errc = 1;
	}
	name = cxf->value.element.name;

	if ((attr = strchr(cxf->value.element.name, ':')) != NULL) {
		int len = attr - name;
		if (len > 99)
			len = 99;
		strncpy(p->pfx, name, len);
		p->pfx[len] = '\000';
	} else {
		p->pfx[0] = '\000';
	}

	a1logd(p->log, 4, "read_cxf: prefix '%s'\n",p->pfx);

	if (strcmp(name, pfx(p, "CxF")) != 0) {
		snprintf(p->err, NAMEDC_ERRL, "Top element not called CxF in '%s'",p->filename);
		a1logd(p->log, 1, "read_cxf: %s\n",p->err);
		mxmlDelete(tree);
		return p->errc = 1;
	}

	/* Check that its CxF3 */
	if ((attr = mxmlElementGetAttr(cxf, sfx(p, "xmlns"))) == NULL) {
		snprintf(p->err, NAMEDC_ERRL, "Failed to find CxF attribute %s in '%s'",sfx(p, "xmlns"), p->filename);
		a1logd(p->log, 1, "read_cxf: %s\n",p->err);
		mxmlDelete(tree);
		return p->errc = 1;
	}

	if (strcmp(attr, "http://colorexchangeformat.com/CxF3-core") != 0) {
		if (strcmp(attr, "http://colorexchangeformat.com/v2") != 0) {
			snprintf(p->err, NAMEDC_ERRL, "File '%s' is not CxF format",p->filename);
			a1logd(p->log, 1, "read_cxf: %s\n",p->err);
			mxmlDelete(tree);
			return p->errc = 1;
		} else {
			cxf2 = 1;
			a1logd(p->log, 1, "read_cxf: This is a CxF2 file\n");
		}
	} else {
		a1logd(p->log, 1, "read_cxf: This is a CxF3 file\n");
	}

	/* Grab the description */
	if (cxf2) {
		if ((node = mxmlFindPathNode(cxf, pfxp(p,"Palette"))) == NULL
		 || (name = mxmlElementGetAttr(node, "PaletteName")) == NULL)
			name = NULL;

		if (name == NULL)
			name = p->filename;
		if ((p->description = strdup(name)) == NULL) {
			snprintf(p->err, NAMEDC_ERRL, "Malloc of description string failed");
			a1logd(p->log, 1, "read_cxf: %s\n",p->err);
			mxmlDelete(tree);
			return p->errc = 2;
		}
		a1logd(p->log, 2, "read_cxf: description '%s'\n",p->description);
	
		p->hash = do_hash2(p->filename, p->description);
	
	} else {	/* else cxf3 */
		if ((node = mxmlFindPathNode(cxf, pfxp(p,"FileInformation/Description"))) == NULL)
			name = NULL;
		else
			name = mxmlGetOpaque(node);
		if (name == NULL)
			name = p->filename;
		if ((p->description = strdup(name)) == NULL) {
			snprintf(p->err, NAMEDC_ERRL, "Malloc of description string failed");
			a1logd(p->log, 1, "read_cxf: %s\n",p->err);
			mxmlDelete(tree);
			return p->errc = 2;
		}
		a1logd(p->log, 2, "read_cxf: description '%s'\n",p->description);
	
		p->hash = do_hash2(p->filename, p->description);
	}

	/* Look through the color specifications and see if there are spectral details */
	pnode = mxmlFindPathNode(cxf, pfxp(p,"Resources/ColorSpecificationCollection/ColorSpecification/WavelengthRange"));
	if (pnode == NULL)
		pnode = mxmlFindPathNode(cxf, pfxp(p,"Resources/ColorSpecificationCollection/ColorSpecification/MeasurementSpec/WavelengthRange"));
	while (pnode != NULL) {
		name = mxmlElementGetAttr(pnode, "StartWL");
		if (name != NULL) {
			specmin = atoi(name);
			a1logd(p->log, 2, "read_cxf: got StartWL %d\n",specmin);
		}
		name = mxmlElementGetAttr(pnode, "Increment");
		if (name != NULL) {
			specinc = atoi(name);
			a1logd(p->log, 2, "read_cxf: got Increment %d\n",specinc);
		}
		pnode = mxmlGetNextSibling(pnode);
	}

	if ((p->options & NAMEDC_OP_NODATA) == 0) {
		char *SampleKey = NULL;
		char *SampleNameKey = NULL;
		char *SampleLabKey = NULL;
		const char *SpectSpecification = NULL;
		char *SampleXYZKey = NULL;
		char *SampleCMYKKey = NULL;
		const char *colorSpecification = NULL;	/* cxf3 ColorSpecification of Lab or XYZ */
		xsp2cie *sp2cie = NULL;

		if (cxf2) {
			/* Locate the Palette/ColorSet node */
			if ((pnode = mxmlFindPathNode(cxf, pfxp(p,"Palette/ColorSet"))) == NULL) {
				snprintf(p->err, NAMEDC_ERRL, "Failed to find Resources/ObjectCollection in '%s'",p->filename);
				a1logd(p->log, 1, "read_cxf: %s\n",p->err);
				mxmlDelete(tree);
				return p->errc = 1;
			}
			SampleKey = "Color";
			SampleNameKey = "ColorName";
			SampleLabKey = "ColorSpaceCIELab";
			SampleXYZKey = "ColorSpaceCIEXYZ";
			SampleCMYKKey = "ColorSpaceCMYK";

		} else {
			/* Locate the Resources/ObjectCollection node */
			if ((pnode = mxmlFindPathNode(cxf, pfxp(p,"Resources/ObjectCollection"))) == NULL) {
				snprintf(p->err, NAMEDC_ERRL, "Failed to find Resources/ObjectCollection in '%s'",p->filename);
				a1logd(p->log, 1, "read_cxf: %s\n",p->err);
				mxmlDelete(tree);
				return p->errc = 1;
			}
			SampleKey = "Object"; 
			SampleNameKey = "Name";
			SampleLabKey = "ColorCIELab";
			SampleXYZKey = "ColorCIEXYZ";
			SampleCMYKKey = "ColorCMYK";
		}

		/* - - - - - - - - - - - - */
		/* Read all the colors. */
		/* Start with first node */
		node = mxmlFindElement(pnode, pnode, pfx(p,SampleKey), NULL, NULL, MXML_DESCEND_FIRST);
		for (i = 0; node != NULL; i++) {
			int j;
		    mxml_node_t *ppvals, *pvals, *val;
			const char *name;
			double Lab[3];
			int Lab_v = 0;
			xspect *sp = NULL;
			double dev[MAX_CHAN];
			int dev_n = 0;
			icColorSpaceSignature devSig = icMaxEnumColorSpace;
			
			if (mxmlGetType(node) != MXML_ELEMENT) {
				a1logd(p->log, DEB6, "read_cxf: skipping non element node type %d\n",mxmlGetType(node));
				goto next;
			}
			a1logd(p->log, DEB6, "read_cxf: read node '%s'\n",node->value.element.name);

			if (strcmp(node->value.element.name, pfx(p,SampleKey)) != 0) {
				a1logd(p->log, DEB6, "read_cxf: skipping non %s node\n",SampleKey);
				goto next; 
			}
 
			if (!cxf2) {
				/* Get the ObjectType attribute of Object */
				if ((attr = mxmlElementGetAttr(node, "ObjectType")) == NULL) {
					a1logd(p->log, DEB6, "read_cxf: skipping node without ObjectType\n");
					goto next;							/* Skip this one */
				}
	
#ifdef NEVER
				/* Check the attribute value (Should we ?) */
				/* Known values are: Standard, Color */
				/* May be Trial, Target, Substrate, Colorant, ... ? */
				if (strcmp(attr, "Standard") != 0
				 && strcmp(attr, "Color") != 0) {
					a1logd(p->log, DEB6, "read_cxf: skipping node with ObjectType = '%s'\n",attr);
					goto next;							/* Skip this one */
				}
#endif
			}

			if ((attr = mxmlElementGetAttr(node, SampleNameKey)) == NULL) {
				a1logd(p->log, DEB6, "read_cxf: skipping without %s\n",SampleNameKey);
				goto next;							/* Skip this one */
			}
			name = attr;

			a1logd(p->log, DEB4, "read_cxf: got color %d name '%s'\n",i,name);

			if (!cxf2) {
				if ((ppvals = mxmlFindElement(node, node, pfx(p,"ColorValues"), NULL, NULL, MXML_DESCEND_FIRST)) == NULL) {
					a1logd(p->log, DEB4, "read_cxf: no reference ColorValuesname  - skipping\n");
					goto next;
				}
			} else {
				ppvals = node;
			}

			/* Read any spectral values */
			if ((p->options & NAMEDC_OP_NOSPEC) == 0
			 && (pvals = mxmlFindElement(ppvals, ppvals, pfx(p,"ReflectanceSpectrum"), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
				const char *elem = NULL;

				a1logd(p->log, 4, "read_cxf: got ReflectanceSpectrum\n");

				/* Check which color specification is being used with XYZ */
				if (SpectSpecification == NULL) {
					SpectSpecification = mxmlElementGetAttr(pvals, "ColorSpecification");
					a1logd(p->log, 4, "read_cxf: got SpectSpecification '%s'\n",SpectSpecification); 
				}

				if ((elem = mxmlElementGetAttr(pvals, "StartWL")) != NULL) {
					int minwl = 0;
					a1logd(p->log, 4, "read_cxf: got StartWL '%s'\n",elem); 
					minwl = atoi(elem);

					if (specmin != 0) {
						if (specmin != minwl) {
							a1logd(p->log,1,"Inconsistent StartWL %d vs. %d from '%s'\n",specmin,minwl,p->filename);
							goto skip_spectral;
						}
					} else {
						specmin = minwl;
					}
				}

				if ((elem = mxmlElementGetAttr(pvals, "Increment")) != NULL) {
					int inc = 0;
					a1logd(p->log, 4, "read_cxf: got Increment '%s'\n",elem); 
					inc = atoi(elem);

					if (specinc != 0) {
						if (specinc != inc) {
							a1logd(p->log,1,"Inconsistent Increment %d vs. %d from '%s'\n",specinc,inc,p->filename);
							goto skip_spectral;
						}
					} else {
						specinc = inc;
					}
				}

				if (specmin == 0 || specinc == 0) {
					a1logd(p->log,1,"Missing: specmin %d specinc %d from '%s'\n",specmin,specinc,p->filename);
					goto skip_spectral;
				}

				/* mxml stashes multiple elements as children.. */
				if ((val = mxmlGetFirstChild(pvals)) == NULL) {
					a1logd(p->log,1,"No spectral values in '%s'\n",p->filename);
					goto skip_spectral;
				}

				if ((sp = calloc(1, sizeof(xspect))) == NULL) {
					snprintf(p->err, NAMEDC_ERRL, "Malloc of xspect failed");
					a1logd(p->log, 1, "read_cxf: %s\n",p->err);
					mxmlDelete(tree);
					return p->errc = 2;
				}

				for (j = 0; j < XSPECT_MAX_BANDS; j++) {
					sp->spec[j] = 100.0 * mxmlGetReal(val);
					a1logd(p->log, 6, "read_cxf: got spect component %d value %f\n",j,sp->spec[j]);
					val = mxmlGetNextSibling(val);
					if (val == NULL)
						break;
				}
				sp->spec_n = j+1;
				sp->spec_wl_short = (double)specmin;
				sp->spec_wl_long = (double)(specmin + (sp->spec_n-1) * specinc);
				sp->norm = 100.0;

				a1logd(p->log, 6, "read_cxf: wl num %d short %f long %f\n",sp->spec_n,sp->spec_wl_short,sp->spec_wl_long);
			}
		  skip_spectral:;

			/* See if there is ColorCIELab */
			if ((pvals = mxmlFindElement(ppvals, ppvals, pfx(p,SampleLabKey), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
				char *key[3] = { "L", "A", "B" };

				/* Check which color specification is being used with Lab */
				if (!cxf2 && colorSpecification == NULL) {
					colorSpecification = mxmlElementGetAttr(pvals, "ColorSpecification");
				}

				for (j = 0; j < 3; j++) {
					if ((val = mxmlFindElement(pvals, pvals, pfx(p, key[j]), NULL, NULL, MXML_DESCEND_FIRST)) == NULL) {
						a1logd(p->log, DEB6, "read_cxf: failed to find ColorCIELab component %s\n",key[j]);
						break;		/* oops */
					}
					Lab[j] = mxmlGetReal(val);
					a1logd(p->log, DEB6, "read_cxf: got ColorCIELab component %s value %f\n",key[j],Lab[j]);
				}
				if (j >= 3)
					Lab_v = 1;
			}

			if (!Lab_v) {

				a1logd(p->log, DEB6, "read_cxf: no valid ColorCIELab value, look for XYZ\n");

				/* See if there is ColorCIEXYZ instead */
				if ((pvals = mxmlFindElement(ppvals, ppvals, pfx(p,SampleXYZKey), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
					char *key[3] = { "X", "Y", "Z" };

					/* Check which color specification is being used with Lab */
					if (!cxf2 && colorSpecification == NULL) {
						colorSpecification = mxmlElementGetAttr(pvals, "ColorSpecification");
					}

					for (j = 0; j < 3; j++) {
						if ((val = mxmlFindElement(pvals, pvals, pfx(p, key[j]), NULL, NULL, MXML_DESCEND_FIRST)) == NULL) {
							a1logd(p->log, DEB6, "read_cxf: failed to find ColorCIEXYZ component %s\n",key[j]);
							break;		/* oops */
						}
						Lab[j] = mxmlGetReal(val);
						a1logd(p->log, DEB6, "read_cxf: got ColorCIEXYZ component %s value %f\n",key[j],Lab[j]);
					}
					if (j >= 3) {
						icmXYZ2Lab(&icmD50, Lab, Lab);
						Lab_v = 1;
					}
				}
			}

			if (sp == NULL && !Lab_v) {
				a1logd(p->log, DEB6, "read_cxf: no spectral or CIE value found - skipping color\n");
				goto next;
			}
		
			/* Read any device color values */
			if (!cxf2) {
				if ((ppvals = mxmlFindElement(node, node, pfx(p,"DeviceColorValues"), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {

					a1logd(p->log, DEB4, "read_cxf: got DeviceColorValues\n");
				}
			}

			if (ppvals != NULL) {

				// ~~99 could add read of other device values here

				/* See if there is ColorCMYK */
				if ((pvals = mxmlFindElement(ppvals, ppvals, pfx(p,SampleCMYKKey), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
					char *key[4] = { "Cyan", "Magenta", "Yellow", "Black" };

					a1logd(p->log, DEB4, "read_cxf: got ColorCMYK\n");
					for (j = 0; j < 4; j++) {
						if ((val = mxmlFindElement(pvals, pvals, pfx(p, key[j]), NULL, NULL, MXML_DESCEND_FIRST)) == NULL) {
							a1logd(p->log, DEB6, "read_cxf: failed to find ColorCMYK component %s\n",key[j]);
							break;		/* oops */
						}
						dev[j] = mxmlGetReal(val);
						a1logd(p->log, DEB6, "read_cxf: got ColorCMYK component %s value %f\n",key[j],dev[j]);
					}
					if (j >= 4) {
						dev_n = j;
						devSig = icSigCmykData;
					}
				}
			}
		
			/* Add an entry */
			if (p->count >= p->count_a) {
				unsigned int count_n;
				count_n = p->count_a + 4096/sizeof(nce);

				a1logd(p->log, 8, "read_cxf: increasing data array size to %d\n",count_n);
				if ((p->data = recalloc(p->data, p->count_a, sizeof(nce), count_n, sizeof(nce))) == NULL) {
					snprintf(p->err, NAMEDC_ERRL, "Malloc of data size %d failed",p->count_a);
					a1logd(p->log, 1, "read_cxf: %s\n",p->err);
					mxmlDelete(tree);
					return p->errc = 2;
				}
				p->count_a = count_n;
			}
			clear_nce(&p->data[p->count]);

			if ((p->data[p->count].name = strdup(name)) == NULL) {
				snprintf(p->err, NAMEDC_ERRL, "Malloc of color name string failed");
				a1logd(p->log, 1, "read_cxf: %s\n",p->err);
				mxmlDelete(tree);
				return p->errc = 2;
			}
			
			if (sp != NULL) {
				p->data[p->count].sp = sp;
			}

			if (Lab_v) {
				p->data[p->count].Lab[0] = Lab[0];
				p->data[p->count].Lab[1] = Lab[1];
				p->data[p->count].Lab[2] = Lab[2];
				p->data[p->count].Lab_v = 1;

			} else {

				/* match() expects lab values */
				if (sp2cie == NULL) {
					if ((sp2cie = new_xsp2cie(p->ill, 0.0, NULL, p->obs, NULL, icSigLabData, 0)) == NULL) {
						snprintf(p->err, NAMEDC_ERRL, "creating spectral conversion failed");
						a1logd(p->log, 1, "read_cxf: %s\n",p->err);
						mxmlDelete(tree);
						return p->errc = 2;
					}
				}
				sp2cie->convert(sp2cie, p->data[p->count].Lab, p->data[p->count].sp); 
				p->data[p->count].Lab_v = 1;
			}

			if (dev_n > 0 && devSig != icMaxEnumColorSpace) {
				for (j = 0; j < dev_n; j++)
					p->data[p->count].dev[j] = dev[j];
				p->data[p->count].dev_n = dev_n;
				p->data[p->count].devSig = devSig;
			} else {
				p->data[p->count].dev_n = 0;
				p->data[p->count].devSig = icMaxEnumColorSpace;
			}

			a1logd(p->log, 8, "read_cxf: added color %d\n",p->count);

			p->count++;

		next:;	/* Next color */
			node = mxmlGetNextSibling(node);
		}

		/* - - - - - - - - - - - - */
		/* Read extra information */
		if (cxf2) {
			/* Grab the creator */
			if ((node = mxmlFindPathNode(cxf, pfxp(p,"Preamble/Header/Creator"))) == NULL)
				name = NULL;
			else
				name = mxmlGetOpaque(node);
	
			if (name == NULL)
				name = "[Unknown]";
			if ((p->creator = strdup(name)) == NULL) {
				snprintf(p->err, NAMEDC_ERRL, "Malloc of creator string failed");
				a1logd(p->log, 1, "read_cxf: %s\n",p->err);
				mxmlDelete(tree);
				return p->errc = 2;
			}
			a1logd(p->log, 2, "read_cxf: creator '%s'\n",p->creator);

			/* Grab the illuminant type */
			if ((node = mxmlFindPathNode(cxf, pfxp(p,"Palette/ColorSet/CollectionColorSpaceSpecification/ColorSpaceSpecificationSpectrumTristimulus/IlluminationOptions/Illuminant"))) == NULL
			 || (name = mxmlGetOpaque(node)) == NULL) {
				a1logd(p->log, 2, "read_cxf: failed to locate Illuminant - assuming D50\n");
				p->ill = icxIT_D50;
			} else {
				if (strcmp(name, "Illuminant_A") == 0)
					p->ill = icxIT_A;
				else if (strcmp(name, "Illuminant_D50") == 0)
					p->ill = icxIT_D50;
				else if (strcmp(name, "Illuminant_D55") == 0)
					p->ill = icxIT_D55;
				else if (strcmp(name, "Illuminant_D65") == 0)
					p->ill = icxIT_D65;
				else if (strcmp(name, "Illuminant_E") == 0)
					p->ill = icxIT_E;
				else {
					a1logd(p->log, 2, "read_cxf: Illuminant '%s' unrecognised\n",name);
					p->ill = icxIT_D50;
				}
			}
			a1logd(p->log, 2, "read_cxf: illuminant '%s'\n",icm2str(icmIlluminant, p->ill));

			/* Grab the observer type */
			if ((node = mxmlFindPathNode(cxf, pfxp(p,"Palette/ColorSet/CollectionColorSpaceSpecification/ColorSpaceSpecificationSpectrumTristimulus/FieldOfView"))) == NULL
			 || (name = mxmlGetOpaque(node)) == NULL) {
				a1logd(p->log, 2, "read_cxf: failed to locate FieldOfView - assuming 2 degree\n");
				p->obs = icxOT_CIE_1931_2;
			} else {
				if (strcmp(name, "FieldOfView_2_Degree") == 0)
					p->obs = icxOT_CIE_1931_2;
				else if (strcmp(name, "FieldOfView_10_Degree") == 0)
					p->obs = icxOT_CIE_1964_10;
				else {
					a1logd(p->log, 2, "read_cxf: Observer '%s' unrecognised\n",name);
					p->obs = icxOT_CIE_1931_2;
				}
			}
			a1logd(p->log, 2, "read_cxf: observer '%s'\n",icm2str(icmStandardObserver, p->obs));

		} else {
			int found_io = 0;

			/* Grab the creator */
			if ((node = mxmlFindPathNode(cxf, pfxp(p,"FileInformation/Creator"))) == NULL)
				name = NULL;
			else
				name = mxmlGetOpaque(node);
	
			if (name == NULL)
				name = "[Unknown]";
			if ((p->creator = strdup(name)) == NULL) {
				snprintf(p->err, NAMEDC_ERRL, "Malloc of creator string failed");
				a1logd(p->log, 1, "read_cxf: %s\n",p->err);
				mxmlDelete(tree);
				return p->errc = 2;
			}
			a1logd(p->log, 2, "read_cxf: creator '%s'\n",p->creator);

			if (colorSpecification != NULL) {
				a1logd(p->log, 2, "read_cxf: colorSpecification '%s'\n",colorSpecification);
			}

			/* Look through the color specifications and find the one that matches */
			/* the Lab or XYZ color specification */
			pnode = mxmlFindPathNode(cxf, pfxp(p,"Resources/ColorSpecificationCollection/ColorSpecification"));
			while (pnode != NULL) {
				name = mxmlElementGetAttr(pnode, "Id");

				if (colorSpecification == NULL
				 || (name != NULL && strcmp(name, colorSpecification) == 0)) {
		
					a1logd(p->log, 2, "read_cxf: found node matching colorSpecification '%s'\n",colorSpecification);

					/* Grab the illuminant type */
					if ((node = mxmlFindPathNode(pnode, pfxp(p,"TristimulusSpec/Illuminant"))) == NULL
					 || (name = mxmlGetOpaque(node)) == NULL) {
						a1logd(p->log, 2, "read_cxf: failed to locate Illuminant - assuming D50\n");
						p->ill = icxIT_D50;
					} else {
						if (strcmp(name, "A") == 0)
							p->ill = icxIT_A;
						else if (strcmp(name, "D50") == 0)
							p->ill = icxIT_D50;
						else if (strcmp(name, "D55") == 0)
							p->ill = icxIT_D55;
						else if (strcmp(name, "D65") == 0)
							p->ill = icxIT_D65;
						else if (strcmp(name, "E") == 0)
							p->ill = icxIT_E;
						else {
							a1logd(p->log, 2, "read_cxf: Illuminant '%s' unrecognised\n",name);
							p->ill = icxIT_D50;
						}
					}
					a1logd(p->log, 2, "read_cxf: illuminant '%s'\n",icm2str(icmIlluminant, p->ill));
		
					/* Grab the first observer type */
					if ((node = mxmlFindPathNode(cxf, pfxp(p,"Resources/ColorSpecificationCollection/ColorSpecification/TristimulusSpec/Observer"))) == NULL
					 || (name = mxmlGetOpaque(node)) == NULL) {
						a1logd(p->log, 2, "read_cxf: failed to locate Observer - assuming 2 degree\n");
						p->obs = icxOT_CIE_1931_2;
					} else {
						if (strcmp(name, "2_Degree") == 0)
							p->obs = icxOT_CIE_1931_2;
						else if (strcmp(name, "10_Degree") == 0)
							p->obs = icxOT_CIE_1964_10;
						else {
							a1logd(p->log, 2, "read_cxf: Observer '%s' unrecognised\n",name);
							p->obs = icxOT_CIE_1931_2;
						}
					}
					a1logd(p->log, 2, "read_cxf: observer '%s'\n",icm2str(icmStandardObserver, p->obs));
					found_io = 1;
					break;
				}
				pnode = mxmlGetNextSibling(pnode);
			}

			if (!found_io) {
				p->ill = icxIT_D50;
				p->obs = icxOT_CIE_1931_2;
				a1logd(p->log, 2, "read_cxf: failed to locate ColorSpecification - assuming D50 2 degree observer\n");
			}

			/* Other values of interest:
			   Resources/ColorSpecificationCollection/ColorSpecification/ColorSpecification/MeasurementSpec/MeasurementType	ie. "Spectrum_Reflectance"
			   Resources/ColorSpecificationCollection/ColorSpecification/ColorSpecification/MeasurementSpec/CalibrationStandard	ie. "GMDI" for Gretag Macbeth Calibration, "XRGA" for X-Rite Graphic Arts Standard.
			   Resources/ColorSpecificationCollection/ColorSpecification/ColorSpecification/MeasurementSpec/Device/DeviceFilter	ie. "Filter_None" "Filter_UVExcluded", 
			 */
		}
	}

	mxmlDelete(tree);

	a1logd(p->log, 1, "read_cxf: done - %d colors\n",p->count);
	return 0;
}

/* Read in a namedc from a ICC file */
/* Return nz on error */
static int read_icc(namedc *p, const char *filename, int options) {
	char *pfilename;
	icmFile *fp;
	icmErr err = { 0, { '\000'} };
	icc *icco;

	a1logd(p->log, 1, "read_icc: file '%s' options 0x%x\n",filename,options);

	if (p->filename == NULL) {
		if ((pfilename = strdup(filename)) == NULL) {
			snprintf(p->err, NAMEDC_ERRL, "Malloc of filename failed");
			a1logd(p->log, 1, "read_icc: %s\n",p->err);
			return p->errc = 2;
		}
		clear_namedc(p);
		p->filename = pfilename;
	}
	p->options = options;
	
	/* Open up the file for reading */
	if ((fp = new_icmFileStd_name(&err,p->filename,"r")) == NULL) {
		snprintf(p->err, NAMEDC_ERRL, "Opening ICC file '%s' failed with %s (0x%x, '%s')",
		                                     p->filename,strerror(errno),err.c,err.m);
		a1logd(p->log, 1, "read_icc: %s\n",p->err);
		return p->errc = 1;
	}

	if ((icco = new_icc(&err)) == NULL) {
		snprintf(p->err, NAMEDC_ERRL, "Creation ICC object failed");
		a1logd(p->log, 1, "read_icc: %s (0x%x, '%s')\n",p->err,err.c,err.m);
		fp->del(fp);
		return p->errc = 1;
	}

	if (icco->read(icco, fp, 0) != 0) {
		snprintf(p->err, NAMEDC_ERRL, "Failed to read '%s'\n",p->filename);
		a1logd(p->log, 1, "read_icc: %s\n",p->err);
		icco->del(icco);
		fp->del(fp);
		return p->errc = 1;
	}

	if (icco->header->deviceClass != icSigNamedColorClass) {
		snprintf(p->err, NAMEDC_ERRL, "Not a named color profile");
		a1logd(p->log, 1, "read_icc: %s\n",p->err);
		icco->del(icco);
		fp->del(fp);
		return p->errc = 1;
	}

	if (icco->header->pcs != icSigXYZData
	 && icco->header->pcs != icSigLabData) {
		snprintf(p->err, NAMEDC_ERRL, "Unrecognised PCS");
		a1logd(p->log, 1, "read_icc: %s\n",p->err);
		icco->del(icco);
		fp->del(fp);
		return p->errc = 1;
	}

	if (icco->find_tag(icco, icSigNamedColor2Tag) != 0) {
		snprintf(p->err, NAMEDC_ERRL, "Can't find ncl2 tag");
		a1logd(p->log, 1, "read_icc: %s\n",p->err);
		icco->del(icco);
		fp->del(fp);
		return p->errc = 1;
	}

	{
		icmTextDescription *txd;

		/* Try and read the tag from the file */
		if ((txd = (icmTextDescription *)icco->read_tag(icco, icSigProfileDescriptionTag)) == NULL) {
			snprintf(p->err, NAMEDC_ERRL, "No description tag");
			a1logd(p->log, 1, "read_icc: %s\n",p->err);
			icco->del(icco);
			fp->del(fp);
			return p->errc = 1;
		}

		if ((p->description = strdup(txd->desc)) == NULL) {
			snprintf(p->err, NAMEDC_ERRL, "Malloc of description string failed");
			a1logd(p->log, 1, "read_icc: %s\n",p->err);
			icco->del(icco);
			fp->del(fp);
			return p->errc = 2;
		}
		a1logd(p->log, 2, "read_icc: description '%s'\n",p->description);
	}

	p->hash = do_hash2(p->filename, p->description);

	if ((p->options & NAMEDC_OP_NODATA) == 0) {
		icmNamedColor *tag;
		char name[3 * 32];
		double Lab[3];
		int Lab_v = 0;
		double dev[MAX_CHAN];
		int dev_n = 0;
		icColorSpaceSignature devSig = icMaxEnumColorSpace;
		int i, j;
		
#ifdef NEVER
		/* See if there is a measurementType tag */
		icmMeasurement *meastag;
		if ((meastag = (icmMeasurement *)icco->read_tag(icco, icSigMeasurementTag)) != NULL) {
			p->ill = meastag->illuminant;
			p->obs = meastag->observer;
			a1logd(p->log, 2, "read_cxf: assuming D50 illuminant and 2 degree observer\n");
		} else {
			p->ill = icIlluminantD50;
			p->obs = icStdObs1931TwoDegrees;
		}
		a1logd(p->log, 2, "read_cxf: illuminant '%s'\n",icm2str(icmIlluminant, p->ill));
		a1logd(p->log, 2, "read_cxf: observer '%s'\n",icm2str(icmStandardObserver, p->obs));
#endif

		if ((tag = (icmNamedColor *)icco->read_tag(icco, icSigNamedColor2Tag)) == NULL) {
			snprintf(p->err, NAMEDC_ERRL, "Can't read ncl2 tag");
			a1logd(p->log, 1, "read_icc: %s\n",p->err);
			icco->del(icco);
			fp->del(fp);
			return p->errc = 1;
		}

		for (i = 0; i < tag->count; i++) {

			/* Get the name */
			strcpy(name, tag->prefix);
			strcat(name, tag->data[i].root);
			strcat(name, tag->suffix);

			a1logd(p->log, DEB4, "read_icc: got color %d name '%s'\n",i,name);

			if (icco->header->pcs == icSigXYZData) {
				Lab[0] = tag->data[i].pcsCoords[0];
				Lab[1] = tag->data[i].pcsCoords[1];
				Lab[2] = tag->data[i].pcsCoords[2];
				icmXYZ2Lab(&icmD50, Lab, Lab);

			} else {		/* Lab */
				Lab[0] = tag->data[i].pcsCoords[0];
				Lab[1] = tag->data[i].pcsCoords[1];
				Lab[2] = tag->data[i].pcsCoords[2];
			}
			Lab_v = 1;
			a1logd(p->log, DEB6, "read_icc: got ColorCIELab value %s\n",icmPdv(3, Lab));

			/* Add any device space */
			devSig = icco->header->colorSpace;
			dev_n = icmCSSig2nchan(devSig);
			for (j = 0; j < dev_n; j++)
				dev[j] = tag->data[i].deviceCoords[j];

			a1logd(p->log, DEB6, "read_icc: got %s value %s\n",
			       icm2str(icmColorSpaceSig, devSig), icmPdv(dev_n, dev));

			/* Add an entry */
			if (p->count >= p->count_a) {
				unsigned int count_n;
				count_n = p->count_a + 4096/sizeof(nce);

				a1logd(p->log, 8, "read_icc: increasing data array size to %d\n",count_n);
				if ((p->data = recalloc(p->data, p->count_a, sizeof(nce), count_n, sizeof(nce))) == NULL) {
					snprintf(p->err, NAMEDC_ERRL, "Malloc of data size %d failed",p->count_a);
					a1logd(p->log, 1, "read_icc: %s\n",p->err);
					icco->del(icco);
					fp->del(fp);
					return p->errc = 2;
				}
				p->count_a = count_n;
			}
			clear_nce(&p->data[p->count]);

			if ((p->data[p->count].name = strdup(name)) == NULL) {
				snprintf(p->err, NAMEDC_ERRL, "Malloc of color name string failed");
				a1logd(p->log, 1, "read_icc: %s\n",p->err);
				icco->del(icco);
				fp->del(fp);
				return p->errc = 2;
			}
			
			if (Lab_v) {
				p->data[p->count].Lab[0] = Lab[0];
				p->data[p->count].Lab[1] = Lab[1];
				p->data[p->count].Lab[2] = Lab[2];
				p->data[p->count].Lab_v = 1;
			}

			if (dev_n > 0 && devSig != icMaxEnumColorSpace) {
				for (j = 0; j < dev_n; j++)
					p->data[p->count].dev[j] = dev[j];
				p->data[p->count].dev_n = dev_n;
				p->data[p->count].devSig = devSig;
			} else {
				p->data[p->count].dev_n = 0;
				p->data[p->count].devSig = icMaxEnumColorSpace;
			}

			a1logd(p->log, 8, "read_icc: added color %d\n",p->count);

			p->count++;

		next:;	/* Next color */
		}
	}

	icco->del(icco);
	fp->del(fp);

	return 0;
}

/* Read any format named color files */
/* Return nz on error */
static int read_nc(namedc *p, const char *filename, int options) {
	int rv;

	if ((p->format == 0 || p->format == 1)
	 && (rv = read_cxf(p, filename, options)) == 0) {
		p->format = 1;
		return rv;
	} 

	if ((p->format == 0 || p->format == 2)
	 && (rv = read_icc(p, filename, options)) == 0) {
		p->format = 2;
		return rv;
	} 

	/* Try other file types here */
	
	return rv;
}


/* Return the index of the best mataching color, -1 on error. */
/* Lab[] is assumed to be D50, 2 degree standard observer based CIE value, */
/* and the spec value should only be provided if this is a reflective or */
/* transmissive measurement, NULL if emissive. */
/* If named color library is expects other than D50, 2 degree, then */
/* it will use the spectral value if not NULL, or chromatically */
/* adapt the Lab value. */
/* deType == 0 DE76 */
/* deType == 1 DE94 */
/* deType == 2 DE2000 */
/* if de != NULL, return the delta E */
int match(struct _namedc *p, double *de, double *pLab, xspect *rspect, int deType) {
	int i, bix = -1;
	double bde = 1e99;
	double Lab[3];

	if (p->filename == NULL) {		/* We haven't been opened */
		snprintf(p->err, NAMEDC_ERRL, "We haven't been opened");
		a1logd(p->log, 1, "match: %s\n",p->err);
		return -1;
	}

	/* If the colors haven't been read yet, read them now */
	if (p->data == NULL || (p->options & NAMEDC_OP_NODATA)) {
		if (read_nc(p, NULL, (p->options & ~NAMEDC_OP_NODATA))) {
			a1logd(p->log, 1, "match: on demand data load failed with '%s'\n",p->err);
			return -1;
		}
		a1logd(p->log, 1, "match: after loading there are %d colors\n",p->count);
	}

	icmCpy3(Lab, pLab);

	if (p->ill != icIlluminantD50 || p->obs != icStdObs1931TwoDegrees) {
		if (rspect != NULL) {

			if (p->sp2cie == NULL) {
				if ((p->sp2cie = new_xsp2cie(p->ill, 0.0, NULL, p->obs, NULL, icSigLabData, 0)) == NULL) {
					snprintf(p->err, NAMEDC_ERRL, "creating spectral conversion failed");
					a1logd(p->log, 1, "match: %s\n",p->err);
					return -1;
					
				}
			}

			/* Convert spectrum to the Lab we want */
			p->sp2cie->convert(p->sp2cie, Lab, rspect); 

		} else {
			if (p->chrom[0][0] <= -1e38) {
				double wXYZ[3];

				// Special case this for a consistent value with ICC profiles
				if (p->obs == icxOT_CIE_1931_2 && p->ill == icxIT_D65) {
					icmCpy3(wXYZ, icmD65_ary3);
				} else { 
					xsp2cie *tt;
					xspect ts;
					// Get the XYZ of the given white point for the illuminant and observer
					if ((tt = new_xsp2cie(p->ill, 0.0, NULL, p->obs, NULL, icSigXYZData, 0)) == NULL) {
						snprintf(p->err, NAMEDC_ERRL, "creating spectral conversion failed");
						a1logd(p->log, 1, "match: %s\n",p->err);
						return -1;
					}
					if (standardIlluminant(&ts, icxIT_E, 0.0)) {
						snprintf(p->err, NAMEDC_ERRL, "match: creating E type spectrum failed");
						a1logd(p->log, 1, "match: %s\n",p->err);
						return -1;
					} 
					tt->convert(tt, wXYZ, &ts);
					tt->del(tt);
				}
				icmAry2XYZ(p->dXYZ, wXYZ);

				/* Chreate chromatic adapation matrix from D50 to named color */
				icmChromAdaptMatrix(ICM_CAM_BRADFORD, p->dXYZ, icmD50, p->chrom);
			}
			icmLab2XYZ(&icmD50, Lab, pLab);
			icmMulBy3x3(Lab, p->chrom, Lab);
			icmXYZ2Lab(&p->dXYZ, Lab, Lab);
		}
	}
 
	if (deType == 0) {
		for (i = 0; i < p->count; i++) {
			double de = icmLabDEsq(Lab, p->data[i].Lab);
			if (de < bde) {
				bde = de;
				bix = i;
			}
		}

	} else if (deType == 1) {
		for (i = 0; i < p->count; i++) {
			double de = icmCIE94sq(Lab, p->data[i].Lab);
			if (de < bde) {
				bde = de;
				bix = i;
			}
		}

	} else if (deType == 2) {
		for (i = 0; i < p->count; i++) {
			double de = icmCIE2Ksq(Lab, p->data[i].Lab);
			if (de < bde) {
				bde = de;
				bix = i;
			}
		}
	} else {
		snprintf(p->err, NAMEDC_ERRL, "Unnown deType %d",deType);
		a1logd(p->log, 1, "match: %s\n",p->err);
		return -1;
	}

	if (bix < 0) {
		snprintf(p->err, NAMEDC_ERRL, "No colors to match against");
		a1logd(p->log, 1, "match: %s\n",p->err);
		return -1;
	}
	if (de != NULL) {
		*de = sqrt(bde);
	}
	return bix;
}

/* Free an entry */
static void clear_nce(nce *p) {
	if (p != NULL) {
		if (p->name != NULL)
			free(p->name);
		p->name = NULL;
		if (p->sp != NULL)
			free(p->sp);
		p->sp = NULL;
	}
}

/* Free the contents */
static void clear_namedc(namedc *p) {
	if (p != NULL) {
		int i;
		if (p->filename != NULL)
			free(p->filename);
		p->filename = NULL;
		if (p->creator != NULL)
			free(p->creator);
		p->creator = NULL;
		if (p->description != NULL)
			free(p->description);
		p->description = NULL;
		if (p->data != NULL) {
			for (i = 0; i < p->count; i++)
				clear_nce(&p->data[i]);
			free(p->data);
			p->data = NULL;
		}
		p->count = 0;
	}
}

/* Delete it */
static void del_namedc(namedc *p) {
	if (p != NULL) {
		clear_namedc(p);
		p->log = del_a1log(p->log);
		if (p->sp2cie != NULL)
			p->sp2cie->del(p->sp2cie);
		free(p);
	}
}

/* Allocate a new, uninitialised namedc */
/* Note thate black and white points aren't allocated */
namedc *new_namedc(a1log *log) {
	namedc *p;

	a1logd(log, 1, "new_cxf\n");

	if ((p = (namedc *)calloc(1, sizeof(namedc))) == NULL) {
		a1logd(p->log, 1, "new_cxf: calloc failed\n");
		return NULL;
	}

	p->log = new_a1log_d(log);

	/* Init method pointers */
	p->del        = del_namedc;
	p->read_cxf   = read_cxf;
	p->read_icc   = read_icc;
	p->read       = read_nc;
	p->match      = match;

	p->chrom[0][0] = -1e38;

	return p;
}


/* =========================================================================== */

#else /* STANDALONE_TEST */

void usage(char *diag, ...) {
	fprintf(stderr,"Test namedc library\n");
	fprintf(stderr,"Author: Graeme W. Gill\n");
	if (diag != NULL) {
		va_list args;
		fprintf(stderr,"  Diagnostic: ");
		va_start(args, diag);
		vfprintf(stderr, diag, args);
		va_end(args);
		fprintf(stderr,"\n");
	}
	fprintf(stderr,"usage: namedc [-D level] infile\n");
	fprintf(stderr," -D level               Debug level 1-9\n");
	exit(1);
}

int
main(int argc, char *argv[]) {
	int fa,nfa;				/* argument we're looking at */
	char inname[MAXNAMEL+1];
	int debug = 0, i;
	namedc *p;

	/* Process the arguments */
	for(fa = 1;fa < argc;fa++) {
		nfa = fa;					/* skip to nfa if next argument is used */
		if (argv[fa][0] == '-')	{	/* Look for any flags */
			char *na = NULL;		/* next argument after flag, null if none */

			if (argv[fa][2] != '\000')
				na = &argv[fa][2];		/* next is directly after flag */
			else {
				if ((fa+1) < argc) {
					if (argv[fa+1][0] != '-') {
						nfa = fa + 1;
						na = argv[nfa];		/* next is seperate non-flag argument */
					}
				}
			}

			if (argv[fa][1] == '?')
				usage(NULL);

			/* Debug level */
			else if (argv[fa][1] == 'D') {
				fa = nfa;
				if (na != NULL) 
					debug = atoi(na);
			}

			else 
				usage("Unknown option '%c'",argv[fa][1]);
		}
		else
			break;
	}

	if (fa >= argc || argv[fa][0] == '-') usage("Missing input filename");
	strncpy(inname,argv[fa++],MAXNAMEL); inname[MAXNAMEL] = '\000';

	g_log->debug = debug;

	if ((p = new_namedc(g_log)) == NULL)
		error("new_namedc failed\n");

	/* Open non-data */
	if (p->read(p, inname, NAMEDC_OP_NODATA)) {
		error("read failed with '%s'\n",p->err);
	}

	printf("Palette is '%s'\n",p->description);
	{
		double Lab[3], de;
		int ix;
		
		Lab[0] = 50.0;
		Lab[1] = 20.0;
		Lab[2] = -10.0;

		if ((ix = p->match(p, &de, Lab, NULL, 0)) < 0)
			error(" match failed with '%s'\n",p->err);
		printf("Matched color '%s' with DE76 %f\n",p->data[ix].name,de);

		if ((ix = p->match(p, &de, Lab, NULL, 1)) < 0)
			error(" match failed with '%s'\n",p->err);
		printf("Matched color '%s' with DE94 %f\n",p->data[ix].name,de);

		if ((ix = p->match(p, &de, Lab, NULL, 2)) < 0)
			error(" match failed with '%s'\n",p->err);
		printf("Matched color '%s' with DE00 %f\n",p->data[ix].name,de);
	}
	
#ifdef NEVER
	printf("Loaded %d colors\n",p->count);
	for (i = 0; i < p->count; i++) {
		printf("Color %d name '%s' = %f %f %f\n",
		       i, p->data[i].name, p->data[i].Lab[0], p->data[i].Lab[1], p->data[i].Lab[2]);
		if (p->data[i].devSig != icMaxEnumColorSpace) {
			printf("%s = %s\n", icm2str(icmColorSpaceSig, p->data[i].devSig),
			             icmPdv(p->data[i].dev_n, p->data[i].dev));
		}
	}
#endif
	p->del(p);

	return 0;
}



#endif /* STANDALONE_TEST */
























