/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
// This file is available under and governed by the GNU General Public
// License version 2 only, as published by the Free Software Foundation.
// However, the following notice accompanied the original version of this
// file:
//
//---------------------------------------------------------------------------------
//
//  Little Color Management System
//  Copyright (c) 1998-2017 Marti Maria Saguer
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//---------------------------------------------------------------------------------
//
#include "lcms2_internal.h"
// IT8.7 / CGATS.17-200x handling -----------------------------------------------------------------------------
#define MAXID        128     // Max length of identifier
#define MAXSTR      1024     // Max length of string
#define MAXTABLES    255     // Max Number of tables in a single stream
#define MAXINCLUDE    20     // Max number of nested includes
#define DEFAULT_DBL_FORMAT  "%.10g" // Double formatting
#ifdef CMS_IS_WINDOWS_
#    include <io.h>
#    define DIR_CHAR    '\\'
#else
#    define DIR_CHAR    '/'
#endif
// Symbols
typedef enum {
        SUNDEFINED,
        SINUM,      // Integer
        SDNUM,      // Real
        SIDENT,     // Identifier
        SSTRING,    // string
        SCOMMENT,   // comment
        SEOLN,      // End of line
        SEOF,       // End of stream
        SSYNERROR,  // Syntax error found on stream
        // Keywords
        SBEGIN_DATA,
        SBEGIN_DATA_FORMAT,
        SEND_DATA,
        SEND_DATA_FORMAT,
        SKEYWORD,
        SDATA_FORMAT_ID,
        SINCLUDE
    } SYMBOL;
// How to write the value
typedef enum {
        WRITE_UNCOOKED,
        WRITE_STRINGIFY,
        WRITE_HEXADECIMAL,
        WRITE_BINARY,
        WRITE_PAIR
    } WRITEMODE;
// Linked list of variable names
typedef struct _KeyVal {
        struct _KeyVal*  Next;
        char*            Keyword;       // Name of variable
        struct _KeyVal*  NextSubkey;    // If key is a dictionary, points to the next item
        char*            Subkey;        // If key is a dictionary, points to the subkey name
        char*            Value;         // Points to value
        WRITEMODE        WriteAs;       // How to write the value
   } KEYVALUE;
// Linked list of memory chunks (Memory sink)
typedef struct _OwnedMem {
        struct _OwnedMem* Next;
        void *            Ptr;          // Point to value
   } OWNEDMEM;
// Suballocator
typedef struct _SubAllocator {
         cmsUInt8Number* Block;
         cmsUInt32Number BlockSize;
         cmsUInt32Number Used;
    } SUBALLOCATOR;
// Table. Each individual table can hold properties and rows & cols
typedef struct _Table {
        char SheetType[MAXSTR];               // The first row of the IT8 (the type)
        int            nSamples, nPatches;    // Cols, Rows
        int            SampleID;              // Pos of ID
        KEYVALUE*      HeaderList;            // The properties
        char**         DataFormat;            // The binary stream descriptor
        char**         Data;                  // The binary stream
    } TABLE;
// File stream being parsed
typedef struct _FileContext {
        char           FileName[cmsMAX_PATH];    // File name if being readed from file
        FILE*          Stream;                   // File stream or NULL if holded in memory
    } FILECTX;
// This struct hold all information about an open IT8 handler.
typedef struct {
        cmsUInt32Number  TablesCount;                     // How many tables in this stream
        cmsUInt32Number  nTable;                          // The actual table
        TABLE Tab[MAXTABLES];
        // Memory management
        OWNEDMEM*      MemorySink;            // The storage backend
        SUBALLOCATOR   Allocator;             // String suballocator -- just to keep it fast
        // Parser state machine
        SYMBOL             sy;                // Current symbol
        int                ch;                // Current character
        cmsInt32Number     inum;              // integer value
        cmsFloat64Number   dnum;              // real value
        char           id[MAXID];             // identifier
        char           str[MAXSTR];           // string
        // Allowed keywords & datasets. They have visibility on whole stream
        KEYVALUE*      ValidKeywords;
        KEYVALUE*      ValidSampleID;
        char*          Source;                // Points to loc. being parsed
        cmsInt32Number lineno;                // line counter for error reporting
        FILECTX*       FileStack[MAXINCLUDE]; // Stack of files being parsed
        cmsInt32Number IncludeSP;             // Include Stack Pointer
        char*          MemoryBlock;           // The stream if holded in memory
        char           DoubleFormatter[MAXID];// Printf-like 'cmsFloat64Number' formatter
        cmsContext    ContextID;              // The threading context
   } cmsIT8;
// The stream for save operations
typedef struct {
        FILE* stream;   // For save-to-file behaviour
        cmsUInt8Number* Base;
        cmsUInt8Number* Ptr;        // For save-to-mem behaviour
        cmsUInt32Number Used;
        cmsUInt32Number Max;
    } SAVESTREAM;
// ------------------------------------------------------ cmsIT8 parsing routines
// A keyword
typedef struct {
        const char *id;
        SYMBOL sy;
   } KEYWORD;
// The keyword->symbol translation table. Sorting is required.
static const KEYWORD TabKeys[] = {
        {"$INCLUDE",               SINCLUDE},   // This is an extension!
        {".INCLUDE",               SINCLUDE},   // This is an extension!
        {"BEGIN_DATA",             SBEGIN_DATA },
        {"BEGIN_DATA_FORMAT",      SBEGIN_DATA_FORMAT },
        {"DATA_FORMAT_IDENTIFIER", SDATA_FORMAT_ID},
        {"END_DATA",               SEND_DATA},
        {"END_DATA_FORMAT",        SEND_DATA_FORMAT},
        {"KEYWORD",                SKEYWORD}
        };
#define NUMKEYS (sizeof(TabKeys)/sizeof(KEYWORD))
// Predefined properties
// A property
typedef struct {
        const char *id;    // The identifier
        WRITEMODE as;      // How is supposed to be written
    } PROPERTY;
static PROPERTY PredefinedProperties[] = {
        {"NUMBER_OF_FIELDS", WRITE_UNCOOKED},    // Required - NUMBER OF FIELDS
        {"NUMBER_OF_SETS",   WRITE_UNCOOKED},    // Required - NUMBER OF SETS
        {"ORIGINATOR",       WRITE_STRINGIFY},   // Required - Identifies the specific system, organization or individual that created the data file.
        {"FILE_DESCRIPTOR",  WRITE_STRINGIFY},   // Required - Describes the purpose or contents of the data file.
        {"CREATED",          WRITE_STRINGIFY},   // Required - Indicates date of creation of the data file.
        {"DESCRIPTOR",       WRITE_STRINGIFY},   // Required  - Describes the purpose or contents of the data file.
        {"DIFFUSE_GEOMETRY", WRITE_STRINGIFY},   // The diffuse geometry used. Allowed values are "sphere" or "opal".
        {"MANUFACTURER",     WRITE_STRINGIFY},
        {"MANUFACTURE",      WRITE_STRINGIFY},   // Some broken Fuji targets does store this value
        {"PROD_DATE",        WRITE_STRINGIFY},   // Identifies year and month of production of the target in the form yyyy:mm.
        {"SERIAL",           WRITE_STRINGIFY},   // Uniquely identifies individual physical target.
        {"MATERIAL",         WRITE_STRINGIFY},    // Identifies the material on which the target was produced using a code
                                                  // uniquely identifying th e material. This is intend ed to be used for IT8.7
                                                  // physical targets only (i.e . IT8.7/1 a nd IT8.7/2).
        {"INSTRUMENTATION",  WRITE_STRINGIFY},    // Used to report the specific instrumentation used (manufacturer and
                                                  // model number) to generate the data reported. This data will often
                                                  // provide more information about the particular data collected than an
                                                  // extensive list of specific details. This is particularly important for
                                                  // spectral data or data derived from spectrophotometry.
        {"MEASUREMENT_SOURCE", WRITE_STRINGIFY},  // Illumination used for spectral measurements. This data helps provide
                                                  // a guide to the potential for issues of paper fluorescence, etc.
        {"PRINT_CONDITIONS", WRITE_STRINGIFY},     // Used to define the characteristics of the printed sheet being reported.
                                                   // Where standard conditions have been defined (e.g., SWOP at nominal)
                                                   // named conditions may suffice. Otherwise, detailed information is
                                                   // needed.
        {"SAMPLE_BACKING",   WRITE_STRINGIFY},     // Identifies the backing material used behind the sample during
                                                   // measurement. Allowed values are 揵lack�, 搘hite�, or {"na".
        {"CHISQ_DOF",        WRITE_STRINGIFY},     // Degrees of freedom associated with the Chi squared statistic
                                                   // below properties are new in recent specs:
        {"MEASUREMENT_GEOMETRY", WRITE_STRINGIFY}, // The type of measurement, either reflection or transmission, should be indicated
                                                   // along with details of the geometry and the aperture size and shape. For example,
                                                   // for transmission measurements it is important to identify 0/diffuse, diffuse/0,
                                                   // opal or integrating sphere, etc. For reflection it is important to identify 0/45,
                                                   // 45/0, sphere (specular included or excluded), etc.
       {"FILTER",            WRITE_STRINGIFY},     // Identifies the use of physical filter(s) during measurement. Typically used to
                                                   // denote the use of filters such as none, D65, Red, Green or Blue.
       {"POLARIZATION",      WRITE_STRINGIFY},     // Identifies the use of a physical polarization filter during measurement. Allowed
                                                   // values are {"yes�, 搘hite�, 搉one� or 搉a�.
       {"WEIGHTING_FUNCTION", WRITE_PAIR},         // Indicates such functions as: the CIE standard observer functions used in the
                                                   // calculation of various data parameters (2 degree and 10 degree), CIE standard
                                                   // illuminant functions used in the calculation of various data parameters (e.g., D50,
                                                   // D65, etc.), density status response, etc. If used there shall be at least one
                                                   // name-value pair following the WEIGHTING_FUNCTION tag/keyword. The first attribute
                                                   // in the set shall be {"name" and shall identify the particular parameter used.
                                                   // The second shall be {"value" and shall provide the value associated with that name.
                                                   // For ASCII data, a string containing the Name and Value attribute pairs shall follow
                                                   // the weighting function keyword. A semi-colon separates attribute pairs from each
                                                   // other and within the attribute the name and value are separated by a comma.
       {"COMPUTATIONAL_PARAMETER", WRITE_PAIR},    // Parameter that is used in computing a value from measured data. Name is the name
                                                   // of the calculation, parameter is the name of the parameter used in the calculation
                                                   // and value is the value of the parameter.
       {"TARGET_TYPE",        WRITE_STRINGIFY},    // The type of target being measured, e.g. IT8.7/1, IT8.7/3, user defined, etc.
       {"COLORANT",           WRITE_STRINGIFY},    // Identifies the colorant(s) used in creating the target.
       {"TABLE_DESCRIPTOR",   WRITE_STRINGIFY},    // Describes the purpose or contents of a data table.
       {"TABLE_NAME",         WRITE_STRINGIFY}     // Provides a short name for a data table.
};
#define NUMPREDEFINEDPROPS (sizeof(PredefinedProperties)/sizeof(PROPERTY))
// Predefined sample types on dataset
static const char* PredefinedSampleID[] = {
        "SAMPLE_ID",      // Identifies sample that data represents
        "STRING",         // Identifies label, or other non-machine readable value.
                          // Value must begin and end with a " symbol
        "CMYK_C",         // Cyan component of CMYK data expressed as a percentage
        "CMYK_M",         // Magenta component of CMYK data expressed as a percentage
        "CMYK_Y",         // Yellow component of CMYK data expressed as a percentage
        "CMYK_K",         // Black component of CMYK data expressed as a percentage
        "D_RED",          // Red filter density
        "D_GREEN",        // Green filter density
        "D_BLUE",         // Blue filter density
        "D_VIS",          // Visual filter density
        "D_MAJOR_FILTER", // Major filter d ensity
        "RGB_R",          // Red component of RGB data
        "RGB_G",          // Green component of RGB data
        "RGB_B",          // Blue com ponent of RGB data
        "SPECTRAL_NM",    // Wavelength of measurement expressed in nanometers
        "SPECTRAL_PCT",   // Percentage reflectance/transmittance
        "SPECTRAL_DEC",   // Reflectance/transmittance
        "XYZ_X",          // X component of tristimulus data
        "XYZ_Y",          // Y component of tristimulus data
        "XYZ_Z",          // Z component of tristimulus data
        "XYY_X",          // x component of chromaticity data
        "XYY_Y",          // y component of chromaticity data
        "XYY_CAPY",       // Y component of tristimulus data
        "LAB_L",          // L* component of Lab data
        "LAB_A",          // a* component of Lab data
        "LAB_B",          // b* component of Lab data
        "LAB_C",          // C*ab component of Lab data
        "LAB_H",          // hab component of Lab data
        "LAB_DE",         // CIE dE
        "LAB_DE_94",      // CIE dE using CIE 94
        "LAB_DE_CMC",     // dE using CMC
        "LAB_DE_2000",    // CIE dE using CIE DE 2000
        "MEAN_DE",        // Mean Delta E (LAB_DE) of samples compared to batch average
                          // (Used for data files for ANSI IT8.7/1 and IT8.7/2 targets)
        "STDEV_X",        // Standard deviation of X (tristimulus data)
        "STDEV_Y",        // Standard deviation of Y (tristimulus data)
        "STDEV_Z",        // Standard deviation of Z (tristimulus data)
        "STDEV_L",        // Standard deviation of L*
        "STDEV_A",        // Standard deviation of a*
        "STDEV_B",        // Standard deviation of b*
        "STDEV_DE",       // Standard deviation of CIE dE
        "CHI_SQD_PAR"};   // The average of the standard deviations of L*, a* and b*. It is
                          // used to derive an estimate of the chi-squared parameter which is
                          // recommended as the predictor of the variability of dE
#define NUMPREDEFINEDSAMPLEID (sizeof(PredefinedSampleID)/sizeof(char *))
//Forward declaration of some internal functions
static void* AllocChunk(cmsIT8* it8, cmsUInt32Number size);
// Checks whatever c is a separator
static
cmsBool isseparator(int c)
{
    return (c == ' ') || (c == '\t') ;
}
// Checks whatever c is a valid identifier char
static
cmsBool ismiddle(int c)
{
   return (!isseparator(c) && (c != '#') && (c !='\"') && (c != '\'') && (c > 32) && (c < 127));
}
// Checks whatsever c is a valid identifier middle char.
static
cmsBool isidchar(int c)
{
   return isalnum(c) || ismiddle(c);
}
// Checks whatsever c is a valid identifier first char.
static
cmsBool isfirstidchar(int c)
{
     return !isdigit(c) && ismiddle(c);
}
// Guess whether the supplied path looks like an absolute path
static
cmsBool isabsolutepath(const char *path)
{
    char ThreeChars[4];
    if(path == NULL)
        return FALSE;
    if (path[0] == 0)
        return FALSE;
    strncpy(ThreeChars, path, 3);
    ThreeChars[3] = 0;
    if(ThreeChars[0] == DIR_CHAR)
        return TRUE;
#ifdef  CMS_IS_WINDOWS_
    if (isalpha((int) ThreeChars[0]) && ThreeChars[1] == ':')
        return TRUE;
#endif
    return FALSE;
}
// Makes a file path based on a given reference path
// NOTE: this function doesn't check if the path exists or even if it's legal
static
cmsBool BuildAbsolutePath(const char *relPath, const char *basePath, char *buffer, cmsUInt32Number MaxLen)
{
    char *tail;
    cmsUInt32Number len;
    // Already absolute?
    if (isabsolutepath(relPath)) {
        strncpy(buffer, relPath, MaxLen);
        buffer[MaxLen-1] = 0;
        return TRUE;
    }
    // No, search for last
    strncpy(buffer, basePath, MaxLen);
    buffer[MaxLen-1] = 0;
    tail = strrchr(buffer, DIR_CHAR);
    if (tail == NULL) return FALSE;    // Is not absolute and has no separators??
    len = (cmsUInt32Number) (tail - buffer);
    if (len >= MaxLen) return FALSE;
    // No need to assure zero terminator over here
    strncpy(tail + 1, relPath, MaxLen - len);
    return TRUE;
}
// Make sure no exploit is being even tried
static
const char* NoMeta(const char* str)
{
    if (strchr(str, '%') != NULL)
        return "**** CORRUPTED FORMAT STRING ***";
    return str;
}
// Syntax error
static
cmsBool SynError(cmsIT8* it8, const char *Txt, ...)
{
    char Buffer[256], ErrMsg[1024];
    va_list args;
    va_start(args, Txt);
    vsnprintf(Buffer, 255, Txt, args);
    Buffer[255] = 0;
    va_end(args);
    snprintf(ErrMsg, 1023, "%s: Line %d, %s", it8->FileStack[it8 ->IncludeSP]->FileName, it8->lineno, Buffer);
    ErrMsg[1023] = 0;
    it8->sy = SSYNERROR;
    cmsSignalError(it8 ->ContextID, cmsERROR_CORRUPTION_DETECTED, "%s", ErrMsg);
    return FALSE;
}
// Check if current symbol is same as specified. issue an error else.
static
cmsBool Check(cmsIT8* it8, SYMBOL sy, const char* Err)
{
        if (it8 -> sy != sy)
                return SynError(it8, NoMeta(Err));
        return TRUE;
}
// Read Next character from stream
static
void NextCh(cmsIT8* it8)
{
    if (it8 -> FileStack[it8 ->IncludeSP]->Stream) {
        it8 ->ch = fgetc(it8 ->FileStack[it8 ->IncludeSP]->Stream);
        if (feof(it8 -> FileStack[it8 ->IncludeSP]->Stream))  {
            if (it8 ->IncludeSP > 0) {
                fclose(it8 ->FileStack[it8->IncludeSP--]->Stream);
                it8 -> ch = ' ';                            // Whitespace to be ignored
            } else
                it8 ->ch = 0;   // EOF
        }
    }
    else {
        it8->ch = *it8->Source;
        if (it8->ch) it8->Source++;
    }
}
// Try to see if current identifier is a keyword, if so return the referred symbol
static
SYMBOL BinSrchKey(const char *id)
{
    int l = 1;
    int r = NUMKEYS;
    int x, res;
    while (r >= l)
    {
        x = (l+r)/2;
        res = cmsstrcasecmp(id, TabKeys[x-1].id);
        if (res == 0) return TabKeys[x-1].sy;
        if (res < 0) r = x - 1;
        else l = x + 1;
    }
    return SUNDEFINED;
}
// 10 ^n
static
cmsFloat64Number xpow10(int n)
{
    return pow(10, (cmsFloat64Number) n);
}
//  Reads a Real number, tries to follow from integer number
static
void ReadReal(cmsIT8* it8, cmsInt32Number inum)
{
    it8->dnum = (cmsFloat64Number)inum;
    while (isdigit(it8->ch)) {
        it8->dnum = (cmsFloat64Number)it8->dnum * 10.0 + (cmsFloat64Number)(it8->ch - '0');
        NextCh(it8);
    }
    if (it8->ch == '.') {        // Decimal point
        cmsFloat64Number frac = 0.0;      // fraction
        int prec = 0;                     // precision
        NextCh(it8);               // Eats dec. point
        while (isdigit(it8->ch)) {
            frac = frac * 10.0 + (cmsFloat64Number)(it8->ch - '0');
            prec++;
            NextCh(it8);
        }
        it8->dnum = it8->dnum + (frac / xpow10(prec));
    }
    // Exponent, example 34.00E+20
    if (toupper(it8->ch) == 'E') {
        cmsInt32Number e;
        cmsInt32Number sgn;
        NextCh(it8); sgn = 1;
        if (it8->ch == '-') {
            sgn = -1; NextCh(it8);
        }
        else
            if (it8->ch == '+') {
                sgn = +1;
                NextCh(it8);
            }
        e = 0;
        while (isdigit(it8->ch)) {
            cmsInt32Number digit = (it8->ch - '0');
            if ((cmsFloat64Number)e * 10.0 + (cmsFloat64Number)digit < (cmsFloat64Number)+2147483647.0)
                e = e * 10 + digit;
            NextCh(it8);
        }
        e = sgn*e;
        it8->dnum = it8->dnum * xpow10(e);
    }
}
// Parses a float number
// This can not call directly atof because it uses locale dependent
// parsing, while CCMX files always use . as decimal separator
static
cmsFloat64Number ParseFloatNumber(const char *Buffer)
{
    cmsFloat64Number dnum = 0.0;
    int sign = 1;
    // keep safe
    if (Buffer == NULL) return 0.0;
    if (*Buffer == '-' || *Buffer == '+') {
        sign = (*Buffer == '-') ? -1 : 1;
        Buffer++;
    }
    while (*Buffer && isdigit((int)*Buffer)) {
        dnum = dnum * 10.0 + (*Buffer - '0');
        if (*Buffer) Buffer++;
    }
    if (*Buffer == '.') {
        cmsFloat64Number frac = 0.0;      // fraction
        int prec = 0;                     // precision
        if (*Buffer) Buffer++;
        while (*Buffer && isdigit((int)*Buffer)) {
            frac = frac * 10.0 + (*Buffer - '0');
            prec++;
            if (*Buffer) Buffer++;
        }
        dnum = dnum + (frac / xpow10(prec));
    }
    // Exponent, example 34.00E+20
    if (*Buffer && toupper(*Buffer) == 'E') {
        int e;
        int sgn;
        if (*Buffer) Buffer++;
        sgn = 1;
        if (*Buffer == '-') {
            sgn = -1;
            if (*Buffer) Buffer++;
        }
        else
            if (*Buffer == '+') {
                sgn = +1;
                if (*Buffer) Buffer++;
            }
        e = 0;
        while (*Buffer && isdigit((int)*Buffer)) {
            cmsInt32Number digit = (*Buffer - '0');
            if ((cmsFloat64Number)e * 10.0 + digit < (cmsFloat64Number)+2147483647.0)
                e = e * 10 + digit;
            if (*Buffer) Buffer++;
        }
        e = sgn*e;
        dnum = dnum * xpow10(e);
    }
    return sign * dnum;
}
// Reads next symbol
static
void InSymbol(cmsIT8* it8)
{
    register char *idptr;
    register int k;
    SYMBOL key;
    int sng;
    do {
        while (isseparator(it8->ch))
            NextCh(it8);
        if (isfirstidchar(it8->ch)) {          // Identifier
            k = 0;
            idptr = it8->id;
            do {
                if (++k < MAXID) *idptr++ = (char) it8->ch;
                NextCh(it8);
            } while (isidchar(it8->ch));
            *idptr = '\0';
            key = BinSrchKey(it8->id);
            if (key == SUNDEFINED) it8->sy = SIDENT;
            else it8->sy = key;
        }
        else                         // Is a number?
            if (isdigit(it8->ch) || it8->ch == '.' || it8->ch == '-' || it8->ch == '+')
            {
                int sign = 1;
                if (it8->ch == '-') {
                    sign = -1;
                    NextCh(it8);
                }
                it8->inum = 0;
                it8->sy   = SINUM;
                if (it8->ch == '0') {          // 0xnnnn (Hexa) or 0bnnnn (Binary)
                    NextCh(it8);
                    if (toupper(it8->ch) == 'X') {
                        int j;
                        NextCh(it8);
                        while (isxdigit(it8->ch))
                        {
                            it8->ch = toupper(it8->ch);
                            if (it8->ch >= 'A' && it8->ch <= 'F')  j = it8->ch -'A'+10;
                            else j = it8->ch - '0';
                            if ((cmsFloat64Number) it8->inum * 16.0 + (cmsFloat64Number) j > (cmsFloat64Number)+2147483647.0)
                            {
                                SynError(it8, "Invalid hexadecimal number");
                                return;
                            }
                            it8->inum = it8->inum * 16 + j;
                            NextCh(it8);
                        }
                        return;
                    }
                    if (toupper(it8->ch) == 'B') {  // Binary
                        int j;
                        NextCh(it8);
                        while (it8->ch == '0' || it8->ch == '1')
                        {
                            j = it8->ch - '0';
                            if ((cmsFloat64Number) it8->inum * 2.0 + j > (cmsFloat64Number)+2147483647.0)
                            {
                                SynError(it8, "Invalid binary number");
                                return;
                            }
                            it8->inum = it8->inum * 2 + j;
                            NextCh(it8);
                        }
                        return;
                    }
                }
                while (isdigit(it8->ch)) {
                    cmsInt32Number digit = (it8->ch - '0');
                    if ((cmsFloat64Number) it8->inum * 10.0 + (cmsFloat64Number) digit > (cmsFloat64Number) +2147483647.0) {
                        ReadReal(it8, it8->inum);
                        it8->sy = SDNUM;
                        it8->dnum *= sign;
                        return;
                    }
                    it8->inum = it8->inum * 10 + digit;
                    NextCh(it8);
                }
                if (it8->ch == '.') {
                    ReadReal(it8, it8->inum);
                    it8->sy = SDNUM;
                    it8->dnum *= sign;
                    return;
                }
                it8 -> inum *= sign;
                // Special case. Numbers followed by letters are taken as identifiers
                if (isidchar(it8 ->ch)) {
                    if (it8 ->sy == SINUM) {
                        snprintf(it8->id, 127, "%d", it8->inum);
                    }
                    else {
                        snprintf(it8->id, 127, it8 ->DoubleFormatter, it8->dnum);
                    }
                    k = (int) strlen(it8 ->id);
                    idptr = it8 ->id + k;
                    do {
                        if (++k < MAXID) *idptr++ = (char) it8->ch;
                        NextCh(it8);
                    } while (isidchar(it8->ch));
                    *idptr = '\0';
                    it8->sy = SIDENT;
                }
                return;
            }
            else
                switch ((int) it8->ch) {
        // EOF marker -- ignore it
        case '\x1a':
            NextCh(it8);
            break;
        // Eof stream markers
        case 0:
        case -1:
            it8->sy = SEOF;
            break;
        // Next line
        case '\r':
            NextCh(it8);
            if (it8 ->ch == '\n')
                NextCh(it8);
            it8->sy = SEOLN;
            it8->lineno++;
            break;
        case '\n':
            NextCh(it8);
            it8->sy = SEOLN;
            it8->lineno++;
            break;
        // Comment
        case '#':
            NextCh(it8);
            while (it8->ch && it8->ch != '\n' && it8->ch != '\r')
                NextCh(it8);
            it8->sy = SCOMMENT;
            break;
        // String.
        case '\'':
        case '\"':
            idptr = it8->str;
            sng = it8->ch;
            k = 0;
            NextCh(it8);
            while (k < (MAXSTR-1) && it8->ch != sng) {
                if (it8->ch == '\n'|| it8->ch == '\r') k = MAXSTR+1;
                else {
                    *idptr++ = (char) it8->ch;
                    NextCh(it8);
                    k++;
                }
            }
            it8->sy = SSTRING;
            *idptr = '\0';
            NextCh(it8);
            break;
        default:
            SynError(it8, "Unrecognized character: 0x%x", it8 ->ch);
            return;
            }
    } while (it8->sy == SCOMMENT);
    // Handle the include special token
    if (it8 -> sy == SINCLUDE) {
                FILECTX* FileNest;
                if(it8 -> IncludeSP >= (MAXINCLUDE-1)) {
                    SynError(it8, "Too many recursion levels");
                    return;
                }
                InSymbol(it8);
                if (!Check(it8, SSTRING, "Filename expected")) return;
                FileNest = it8 -> FileStack[it8 -> IncludeSP + 1];
                if(FileNest == NULL) {
                    FileNest = it8 ->FileStack[it8 -> IncludeSP + 1] = (FILECTX*)AllocChunk(it8, sizeof(FILECTX));
                    //if(FileNest == NULL)
                    //  TODO: how to manage out-of-memory conditions?
                }
                if (BuildAbsolutePath(it8->str,
                                      it8->FileStack[it8->IncludeSP]->FileName,
                                      FileNest->FileName, cmsMAX_PATH-1) == FALSE) {
                    SynError(it8, "File path too long");
                    return;
                }
                FileNest->Stream = fopen(FileNest->FileName, "rt");
                if (FileNest->Stream == NULL) {
                        SynError(it8, "File %s not found", FileNest->FileName);
                        return;
                }
                it8->IncludeSP++;
                it8 ->ch = ' ';
                InSymbol(it8);
    }
}
// Checks end of line separator
static
cmsBool CheckEOLN(cmsIT8* it8)
{
        if (!Check(it8, SEOLN, "Expected separator")) return FALSE;
        while (it8 -> sy == SEOLN)
                        InSymbol(it8);
        return TRUE;
}
// Skip a symbol
static
void Skip(cmsIT8* it8, SYMBOL sy)
{
        if (it8->sy == sy && it8->sy != SEOF)
                        InSymbol(it8);
}
// Skip multiple EOLN
static
void SkipEOLN(cmsIT8* it8)
{
    while (it8->sy == SEOLN) {
             InSymbol(it8);
    }
}
// Returns a string holding current value
static
cmsBool GetVal(cmsIT8* it8, char* Buffer, cmsUInt32Number max, const char* ErrorTitle)
{
    switch (it8->sy) {
    case SEOLN:   // Empty value
                  Buffer[0]=0;
                  break;
    case SIDENT:  strncpy(Buffer, it8->id, max);
                  Buffer[max-1]=0;
                  break;
    case SINUM:   snprintf(Buffer, max, "%d", it8 -> inum); break;
    case SDNUM:   snprintf(Buffer, max, it8->DoubleFormatter, it8 -> dnum); break;
    case SSTRING: strncpy(Buffer, it8->str, max);
                  Buffer[max-1] = 0;
                  break;
    default:
         return SynError(it8, "%s", ErrorTitle);
    }
    Buffer[max] = 0;
    return TRUE;
}
// ---------------------------------------------------------- Table
static
TABLE* GetTable(cmsIT8* it8)
{
   if ((it8 -> nTable >= it8 ->TablesCount)) {
           SynError(it8, "Table %d out of sequence", it8 -> nTable);
           return it8 -> Tab;
   }
   return it8 ->Tab + it8 ->nTable;
}
// ---------------------------------------------------------- Memory management
// Frees an allocator and owned memory
void CMSEXPORT cmsIT8Free(cmsHANDLE hIT8)
{
   cmsIT8* it8 = (cmsIT8*) hIT8;
    if (it8 == NULL)
        return;
    if (it8->MemorySink) {
        OWNEDMEM* p;
        OWNEDMEM* n;
        for (p = it8->MemorySink; p != NULL; p = n) {
            n = p->Next;
            if (p->Ptr) _cmsFree(it8 ->ContextID, p->Ptr);
            _cmsFree(it8 ->ContextID, p);
        }
    }
    if (it8->MemoryBlock)
        _cmsFree(it8 ->ContextID, it8->MemoryBlock);
    _cmsFree(it8 ->ContextID, it8);
}
// Allocates a chunk of data, keep linked list
static
void* AllocBigBlock(cmsIT8* it8, cmsUInt32Number size)
{
    OWNEDMEM* ptr1;
    void* ptr = _cmsMallocZero(it8->ContextID, size);
    if (ptr != NULL) {
        ptr1 = (OWNEDMEM*) _cmsMallocZero(it8 ->ContextID, sizeof(OWNEDMEM));
        if (ptr1 == NULL) {
            _cmsFree(it8 ->ContextID, ptr);
            return NULL;
        }
        ptr1-> Ptr        = ptr;
        ptr1-> Next       = it8 -> MemorySink;
        it8 -> MemorySink = ptr1;
    }
    return ptr;
}
// Suballocator.
static
void* AllocChunk(cmsIT8* it8, cmsUInt32Number size)
{
    cmsUInt32Number Free = it8 ->Allocator.BlockSize - it8 ->Allocator.Used;
    cmsUInt8Number* ptr;
    size = _cmsALIGNMEM(size);
    if (size > Free) {
        if (it8 -> Allocator.BlockSize == 0)
                it8 -> Allocator.BlockSize = 20*1024;
        else
                it8 ->Allocator.BlockSize *= 2;
        if (it8 ->Allocator.BlockSize < size)
                it8 ->Allocator.BlockSize = size;
        it8 ->Allocator.Used = 0;
        it8 ->Allocator.Block = (cmsUInt8Number*)  AllocBigBlock(it8, it8 ->Allocator.BlockSize);
    }
    ptr = it8 ->Allocator.Block + it8 ->Allocator.Used;
    it8 ->Allocator.Used += size;
    return (void*) ptr;
}
// Allocates a string
static
char *AllocString(cmsIT8* it8, const char* str)
{
    cmsUInt32Number Size = (cmsUInt32Number) strlen(str)+1;
    char *ptr;
    ptr = (char *) AllocChunk(it8, Size);
    if (ptr) strncpy (ptr, str, Size-1);
    return ptr;
}
// Searches through linked list
static
cmsBool IsAvailableOnList(KEYVALUE* p, const char* Key, const char* Subkey, KEYVALUE** LastPtr)
{
    if (LastPtr) *LastPtr = p;
    for (;  p != NULL; p = p->Next) {
        if (LastPtr) *LastPtr = p;
        if (*Key != '#') { // Comments are ignored
            if (cmsstrcasecmp(Key, p->Keyword) == 0)
                break;
        }
    }
    if (p == NULL)
        return FALSE;
    if (Subkey == 0)
        return TRUE;
    for (; p != NULL; p = p->NextSubkey) {
        if (p ->Subkey == NULL) continue;
        if (LastPtr) *LastPtr = p;
        if (cmsstrcasecmp(Subkey, p->Subkey) == 0)
            return TRUE;
    }
    return FALSE;
}
// Add a property into a linked list
static
KEYVALUE* AddToList(cmsIT8* it8, KEYVALUE** Head, const char *Key, const char *Subkey, const char* xValue, WRITEMODE WriteAs)
{
    KEYVALUE* p;
    KEYVALUE* last;
    // Check if property is already in list
    if (IsAvailableOnList(*Head, Key, Subkey, &p)) {
        // This may work for editing properties
        //     return SynError(it8, "duplicate key <%s>", Key);
    }
    else {
        last = p;
        // Allocate the container
        p = (KEYVALUE*) AllocChunk(it8, sizeof(KEYVALUE));
        if (p == NULL)
        {
            SynError(it8, "AddToList: out of memory");
            return NULL;
        }
        // Store name and value
        p->Keyword = AllocString(it8, Key);
        p->Subkey = (Subkey == NULL) ? NULL : AllocString(it8, Subkey);
        // Keep the container in our list
        if (*Head == NULL) {
            *Head = p;
        }
        else
        {
            if (Subkey != NULL && last != NULL) {
                last->NextSubkey = p;
                // If Subkey is not null, then last is the last property with the same key,
                // but not necessarily is the last property in the list, so we need to move
                // to the actual list end
                while (last->Next != NULL)
                         last = last->Next;
            }
            if (last != NULL) last->Next = p;
        }
        p->Next    = NULL;
        p->NextSubkey = NULL;
    }
    p->WriteAs = WriteAs;
    if (xValue != NULL) {
        p->Value   = AllocString(it8, xValue);
    }
    else {
        p->Value   = NULL;
    }
    return p;
}
static
KEYVALUE* AddAvailableProperty(cmsIT8* it8, const char* Key, WRITEMODE as)
{
    return AddToList(it8, &it8->ValidKeywords, Key, NULL, NULL, as);
}
static
KEYVALUE* AddAvailableSampleID(cmsIT8* it8, const char* Key)
{
    return AddToList(it8, &it8->ValidSampleID, Key, NULL, NULL, WRITE_UNCOOKED);
}
static
void AllocTable(cmsIT8* it8)
{
    TABLE* t;
    t = it8 ->Tab + it8 ->TablesCount;
    t->HeaderList = NULL;
    t->DataFormat = NULL;
    t->Data       = NULL;
    it8 ->TablesCount++;
}
cmsInt32Number CMSEXPORT cmsIT8SetTable(cmsHANDLE  IT8, cmsUInt32Number nTable)
{
     cmsIT8* it8 = (cmsIT8*) IT8;
     if (nTable >= it8 ->TablesCount) {
         if (nTable == it8 ->TablesCount) {
             AllocTable(it8);
         }
         else {
             SynError(it8, "Table %d is out of sequence", nTable);
             return -1;
         }
     }
     it8 ->nTable = nTable;
     return (cmsInt32Number) nTable;
}
// Init an empty container
cmsHANDLE  CMSEXPORT cmsIT8Alloc(cmsContext ContextID)
{
    cmsIT8* it8;
    cmsUInt32Number i;
    it8 = (cmsIT8*) _cmsMallocZero(ContextID, sizeof(cmsIT8));
    if (it8 == NULL) return NULL;
    AllocTable(it8);
    it8->MemoryBlock = NULL;
    it8->MemorySink  = NULL;
    it8 ->nTable = 0;
    it8->ContextID = ContextID;
    it8->Allocator.Used = 0;
    it8->Allocator.Block = NULL;
    it8->Allocator.BlockSize = 0;
    it8->ValidKeywords = NULL;
    it8->ValidSampleID = NULL;
    it8 -> sy = SUNDEFINED;
    it8 -> ch = ' ';
    it8 -> Source = NULL;
    it8 -> inum = 0;
    it8 -> dnum = 0.0;
    it8->FileStack[0] = (FILECTX*)AllocChunk(it8, sizeof(FILECTX));
    it8->IncludeSP   = 0;
    it8 -> lineno = 1;
    strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT);
    cmsIT8SetSheetType((cmsHANDLE) it8, "CGATS.17");
    // Initialize predefined properties & data
    for (i=0; i < NUMPREDEFINEDPROPS; i++)
            AddAvailableProperty(it8, PredefinedProperties[i].id, PredefinedProperties[i].as);
    for (i=0; i < NUMPREDEFINEDSAMPLEID; i++)
            AddAvailableSampleID(it8, PredefinedSampleID[i]);
   return (cmsHANDLE) it8;
}
const char* CMSEXPORT cmsIT8GetSheetType(cmsHANDLE hIT8)
{
        return GetTable((cmsIT8*) hIT8)->SheetType;
}
cmsBool CMSEXPORT cmsIT8SetSheetType(cmsHANDLE hIT8, const char* Type)
{
        TABLE* t = GetTable((cmsIT8*) hIT8);
        strncpy(t ->SheetType, Type, MAXSTR-1);
        t ->SheetType[MAXSTR-1] = 0;
        return TRUE;
}
cmsBool CMSEXPORT cmsIT8SetComment(cmsHANDLE hIT8, const char* Val)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    if (!Val) return FALSE;
    if (!*Val) return FALSE;
    return AddToList(it8, &GetTable(it8)->HeaderList, "# ", NULL, Val, WRITE_UNCOOKED) != NULL;
}
// Sets a property
cmsBool CMSEXPORT cmsIT8SetPropertyStr(cmsHANDLE hIT8, const char* Key, const char *Val)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    if (!Val) return FALSE;
    if (!*Val) return FALSE;
    return AddToList(it8, &GetTable(it8)->HeaderList, Key, NULL, Val, WRITE_STRINGIFY) != NULL;
}
cmsBool CMSEXPORT cmsIT8SetPropertyDbl(cmsHANDLE hIT8, const char* cProp, cmsFloat64Number Val)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    char Buffer[1024];
    snprintf(Buffer, 1023, it8->DoubleFormatter, Val);
    return AddToList(it8, &GetTable(it8)->HeaderList, cProp, NULL, Buffer, WRITE_UNCOOKED) != NULL;
}
cmsBool CMSEXPORT cmsIT8SetPropertyHex(cmsHANDLE hIT8, const char* cProp, cmsUInt32Number Val)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    char Buffer[1024];
    snprintf(Buffer, 1023, "%u", Val);
    return AddToList(it8, &GetTable(it8)->HeaderList, cProp, NULL, Buffer, WRITE_HEXADECIMAL) != NULL;
}
cmsBool CMSEXPORT cmsIT8SetPropertyUncooked(cmsHANDLE hIT8, const char* Key, const char* Buffer)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    return AddToList(it8, &GetTable(it8)->HeaderList, Key, NULL, Buffer, WRITE_UNCOOKED) != NULL;
}
cmsBool CMSEXPORT cmsIT8SetPropertyMulti(cmsHANDLE hIT8, const char* Key, const char* SubKey, const char *Buffer)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    return AddToList(it8, &GetTable(it8)->HeaderList, Key, SubKey, Buffer, WRITE_PAIR) != NULL;
}
// Gets a property
const char* CMSEXPORT cmsIT8GetProperty(cmsHANDLE hIT8, const char* Key)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    KEYVALUE* p;
    if (IsAvailableOnList(GetTable(it8) -> HeaderList, Key, NULL, &p))
    {
        return p -> Value;
    }
    return NULL;
}
cmsFloat64Number CMSEXPORT cmsIT8GetPropertyDbl(cmsHANDLE hIT8, const char* cProp)
{
    const char *v = cmsIT8GetProperty(hIT8, cProp);
    if (v == NULL) return 0.0;
    return ParseFloatNumber(v);
}
const char* CMSEXPORT cmsIT8GetPropertyMulti(cmsHANDLE hIT8, const char* Key, const char *SubKey)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    KEYVALUE* p;
    if (IsAvailableOnList(GetTable(it8) -> HeaderList, Key, SubKey, &p)) {
        return p -> Value;
    }
    return NULL;
}
// ----------------------------------------------------------------- Datasets
static
void AllocateDataFormat(cmsIT8* it8)
{
    TABLE* t = GetTable(it8);
    if (t -> DataFormat) return;    // Already allocated
    t -> nSamples  = (int) cmsIT8GetPropertyDbl(it8, "NUMBER_OF_FIELDS");
    if (t -> nSamples <= 0) {
        SynError(it8, "AllocateDataFormat: Unknown NUMBER_OF_FIELDS");
        t -> nSamples = 10;
        }
    t -> DataFormat = (char**) AllocChunk (it8, ((cmsUInt32Number) t->nSamples + 1) * sizeof(char *));
    if (t->DataFormat == NULL) {
        SynError(it8, "AllocateDataFormat: Unable to allocate dataFormat array");
    }
}
static
const char *GetDataFormat(cmsIT8* it8, int n)
{
    TABLE* t = GetTable(it8);
    if (t->DataFormat)
        return t->DataFormat[n];
    return NULL;
}
static
cmsBool SetDataFormat(cmsIT8* it8, int n, const char *label)
{
    TABLE* t = GetTable(it8);
    if (!t->DataFormat)
        AllocateDataFormat(it8);
    if (n > t -> nSamples) {
        SynError(it8, "More than NUMBER_OF_FIELDS fields.");
        return FALSE;
    }
    if (t->DataFormat) {
        t->DataFormat[n] = AllocString(it8, label);
    }
    return TRUE;
}
cmsBool CMSEXPORT cmsIT8SetDataFormat(cmsHANDLE  h, int n, const char *Sample)
{
    cmsIT8* it8 = (cmsIT8*)h;
    return SetDataFormat(it8, n, Sample);
}
static
void AllocateDataSet(cmsIT8* it8)
{
    TABLE* t = GetTable(it8);
    if (t -> Data) return;    // Already allocated
    t-> nSamples   = atoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
    t-> nPatches   = atoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
    if (t -> nSamples < 0 || t->nSamples > 0x7ffe || t->nPatches < 0 || t->nPatches > 0x7ffe)
    {
        SynError(it8, "AllocateDataSet: too much data");
    }
    else {
        t->Data = (char**)AllocChunk(it8, ((cmsUInt32Number)t->nSamples + 1) * ((cmsUInt32Number)t->nPatches + 1) * sizeof(char*));
        if (t->Data == NULL) {
            SynError(it8, "AllocateDataSet: Unable to allocate data array");
        }
    }
}
static
char* GetData(cmsIT8* it8, int nSet, int nField)
{
    TABLE* t = GetTable(it8);
    int nSamples    = t -> nSamples;
    int nPatches    = t -> nPatches;
    if (nSet >= nPatches || nField >= nSamples)
        return NULL;
    if (!t->Data) return NULL;
    return t->Data [nSet * nSamples + nField];
}
static
cmsBool SetData(cmsIT8* it8, int nSet, int nField, const char *Val)
{
    TABLE* t = GetTable(it8);
    if (!t->Data)
        AllocateDataSet(it8);
    if (!t->Data) return FALSE;
    if (nSet > t -> nPatches || nSet < 0) {
            return SynError(it8, "Patch %d out of range, there are %d patches", nSet, t -> nPatches);
    }
    if (nField > t ->nSamples || nField < 0) {
            return SynError(it8, "Sample %d out of range, there are %d samples", nField, t ->nSamples);
    }
    t->Data [nSet * t -> nSamples + nField] = AllocString(it8, Val);
    return TRUE;
}
// --------------------------------------------------------------- File I/O
// Writes a string to file
static
void WriteStr(SAVESTREAM* f, const char *str)
{
    cmsUInt32Number len;
    if (str == NULL)
        str = " ";
    // Length to write
    len = (cmsUInt32Number) strlen(str);
    f ->Used += len;
    if (f ->stream) {   // Should I write it to a file?
        if (fwrite(str, 1, len, f->stream) != len) {
            cmsSignalError(0, cmsERROR_WRITE, "Write to file error in CGATS parser");
            return;
        }
    }
    else {  // Or to a memory block?
        if (f ->Base) {   // Am I just counting the bytes?
            if (f ->Used > f ->Max) {
                 cmsSignalError(0, cmsERROR_WRITE, "Write to memory overflows in CGATS parser");
                 return;
            }
            memmove(f ->Ptr, str, len);
            f->Ptr += len;
        }
    }
}
// Write formatted
static
void Writef(SAVESTREAM* f, const char* frm, ...)
{
    char Buffer[4096];
    va_list args;
    va_start(args, frm);
    vsnprintf(Buffer, 4095, frm, args);
    Buffer[4095] = 0;
    WriteStr(f, Buffer);
    va_end(args);
}
// Writes full header
static
void WriteHeader(cmsIT8* it8, SAVESTREAM* fp)
{
    KEYVALUE* p;
    TABLE* t = GetTable(it8);
    // Writes the type
    WriteStr(fp, t->SheetType);
    WriteStr(fp, "\n");
    for (p = t->HeaderList; (p != NULL); p = p->Next)
    {
        if (*p ->Keyword == '#') {
            char* Pt;
            WriteStr(fp, "#\n# ");
            for (Pt = p ->Value; *Pt; Pt++) {
                Writef(fp, "%c", *Pt);
                if (*Pt == '\n') {
                    WriteStr(fp, "# ");
                }
            }
            WriteStr(fp, "\n#\n");
            continue;
        }
        if (!IsAvailableOnList(it8-> ValidKeywords, p->Keyword, NULL, NULL)) {
#ifdef CMS_STRICT_CGATS
            WriteStr(fp, "KEYWORD\t\"");
            WriteStr(fp, p->Keyword);
            WriteStr(fp, "\"\n");
#endif
            AddAvailableProperty(it8, p->Keyword, WRITE_UNCOOKED);
        }
        WriteStr(fp, p->Keyword);
        if (p->Value) {
            switch (p ->WriteAs) {
            case WRITE_UNCOOKED:
                    Writef(fp, "\t%s", p ->Value);
                    break;
            case WRITE_STRINGIFY:
                    Writef(fp, "\t\"%s\"", p->Value );
                    break;
            case WRITE_HEXADECIMAL:
                    Writef(fp, "\t0x%X", atoi(p ->Value));
                    break;
            case WRITE_BINARY:
                    Writef(fp, "\t0x%B", atoi(p ->Value));
                    break;
            case WRITE_PAIR:
                    Writef(fp, "\t\"%s,%s\"", p->Subkey, p->Value);
                    break;
            default: SynError(it8, "Unknown write mode %d", p ->WriteAs);
                     return;
            }
        }
        WriteStr (fp, "\n");
    }
}
// Writes the data format
static
void WriteDataFormat(SAVESTREAM* fp, cmsIT8* it8)
{
    int i, nSamples;
    TABLE* t = GetTable(it8);
    if (!t -> DataFormat) return;
       WriteStr(fp, "BEGIN_DATA_FORMAT\n");
       WriteStr(fp, " ");
       nSamples = atoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
       for (i = 0; i < nSamples; i++) {
              WriteStr(fp, t->DataFormat[i]);
              WriteStr(fp, ((i == (nSamples-1)) ? "\n" : "\t"));
          }
       WriteStr (fp, "END_DATA_FORMAT\n");
}
// Writes data array
static
void WriteData(SAVESTREAM* fp, cmsIT8* it8)
{
       int  i, j;
       TABLE* t = GetTable(it8);
       if (!t->Data) return;
       WriteStr (fp, "BEGIN_DATA\n");
       t->nPatches = atoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
       for (i = 0; i < t-> nPatches; i++) {
              WriteStr(fp, " ");
              for (j = 0; j < t->nSamples; j++) {
                     char *ptr = t->Data[i*t->nSamples+j];
                     if (ptr == NULL) WriteStr(fp, "\"\"");
                     else {
                         // If value contains whitespace, enclose within quote
                         if (strchr(ptr, ' ') != NULL) {
                             WriteStr(fp, "\"");
                             WriteStr(fp, ptr);
                             WriteStr(fp, "\"");
                         }
                         else
                            WriteStr(fp, ptr);
                     }
                     WriteStr(fp, ((j == (t->nSamples-1)) ? "\n" : "\t"));
              }
       }
       WriteStr (fp, "END_DATA\n");
}
// Saves whole file
cmsBool CMSEXPORT cmsIT8SaveToFile(cmsHANDLE hIT8, const char* cFileName)
{
    SAVESTREAM sd;
    cmsUInt32Number i;
    cmsIT8* it8 = (cmsIT8*) hIT8;
    memset(&sd, 0, sizeof(sd));
    sd.stream = fopen(cFileName, "wt");
    if (!sd.stream) return FALSE;
    for (i=0; i < it8 ->TablesCount; i++) {
            cmsIT8SetTable(hIT8, i);
            WriteHeader(it8, &sd);
            WriteDataFormat(&sd, it8);
            WriteData(&sd, it8);
    }
    if (fclose(sd.stream) != 0) return FALSE;
    return TRUE;
}
// Saves to memory
cmsBool CMSEXPORT cmsIT8SaveToMem(cmsHANDLE hIT8, void *MemPtr, cmsUInt32Number* BytesNeeded)
{
    SAVESTREAM sd;
    cmsUInt32Number i;
    cmsIT8* it8 = (cmsIT8*) hIT8;
    memset(&sd, 0, sizeof(sd));
    sd.stream = NULL;
    sd.Base   = (cmsUInt8Number*)  MemPtr;
    sd.Ptr    = sd.Base;
    sd.Used = 0;
    if (sd.Base)
        sd.Max  = *BytesNeeded;     // Write to memory?
    else
        sd.Max  = 0;                // Just counting the needed bytes
    for (i=0; i < it8 ->TablesCount; i++) {
        cmsIT8SetTable(hIT8, i);
        WriteHeader(it8, &sd);
        WriteDataFormat(&sd, it8);
        WriteData(&sd, it8);
    }
    sd.Used++;  // The \0 at the very end
    if (sd.Base)
        *sd.Ptr = 0;
    *BytesNeeded = sd.Used;
    return TRUE;
}
// -------------------------------------------------------------- Higher level parsing
static
cmsBool DataFormatSection(cmsIT8* it8)
{
    int iField = 0;
    TABLE* t = GetTable(it8);
    InSymbol(it8);   // Eats "BEGIN_DATA_FORMAT"
    CheckEOLN(it8);
    while (it8->sy != SEND_DATA_FORMAT &&
        it8->sy != SEOLN &&
        it8->sy != SEOF &&
        it8->sy != SSYNERROR)  {
            if (it8->sy != SIDENT) {
                return SynError(it8, "Sample type expected");
            }
            if (!SetDataFormat(it8, iField, it8->id)) return FALSE;
            iField++;
            InSymbol(it8);
            SkipEOLN(it8);
       }
       SkipEOLN(it8);
       Skip(it8, SEND_DATA_FORMAT);
       SkipEOLN(it8);
       if (iField != t ->nSamples) {
           SynError(it8, "Count mismatch. NUMBER_OF_FIELDS was %d, found %d\n", t ->nSamples, iField);
       }
       return TRUE;
}
static
cmsBool DataSection (cmsIT8* it8)
{
    int  iField = 0;
    int  iSet   = 0;
    char Buffer[256];
    TABLE* t = GetTable(it8);
    InSymbol(it8);   // Eats "BEGIN_DATA"
    CheckEOLN(it8);
    if (!t->Data)
        AllocateDataSet(it8);
    while (it8->sy != SEND_DATA && it8->sy != SEOF)
    {
        if (iField >= t -> nSamples) {
            iField = 0;
            iSet++;
        }
        if (it8->sy != SEND_DATA && it8->sy != SEOF) {
            if (!GetVal(it8, Buffer, 255, "Sample data expected"))
                return FALSE;
            if (!SetData(it8, iSet, iField, Buffer))
                return FALSE;
            iField++;
            InSymbol(it8);
            SkipEOLN(it8);
        }
    }
    SkipEOLN(it8);
    Skip(it8, SEND_DATA);
    SkipEOLN(it8);
    // Check for data completion.
    if ((iSet+1) != t -> nPatches)
        return SynError(it8, "Count mismatch. NUMBER_OF_SETS was %d, found %d\n", t ->nPatches, iSet+1);
    return TRUE;
}
static
cmsBool HeaderSection(cmsIT8* it8)
{
    char VarName[MAXID];
    char Buffer[MAXSTR];
    KEYVALUE* Key;
        while (it8->sy != SEOF &&
               it8->sy != SSYNERROR &&
               it8->sy != SBEGIN_DATA_FORMAT &&
               it8->sy != SBEGIN_DATA) {
        switch (it8 -> sy) {
        case SKEYWORD:
                InSymbol(it8);
                if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
                if (!AddAvailableProperty(it8, Buffer, WRITE_UNCOOKED)) return FALSE;
                InSymbol(it8);
                break;
        case SDATA_FORMAT_ID:
                InSymbol(it8);
                if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
                if (!AddAvailableSampleID(it8, Buffer)) return FALSE;
                InSymbol(it8);
                break;
        case SIDENT:
            strncpy(VarName, it8->id, MAXID - 1);
            VarName[MAXID - 1] = 0;
            if (!IsAvailableOnList(it8->ValidKeywords, VarName, NULL, &Key)) {
#ifdef CMS_STRICT_CGATS
                return SynError(it8, "Undefined keyword '%s'", VarName);
#else
                Key = AddAvailableProperty(it8, VarName, WRITE_UNCOOKED);
                if (Key == NULL) return FALSE;
#endif
            }
            InSymbol(it8);
            if (!GetVal(it8, Buffer, MAXSTR - 1, "Property data expected")) return FALSE;
            if (Key->WriteAs != WRITE_PAIR) {
                AddToList(it8, &GetTable(it8)->HeaderList, VarName, NULL, Buffer,
                    (it8->sy == SSTRING) ? WRITE_STRINGIFY : WRITE_UNCOOKED);
            }
            else {
                const char *Subkey;
                char *Nextkey;
                if (it8->sy != SSTRING)
                    return SynError(it8, "Invalid value '%s' for property '%s'.", Buffer, VarName);
                // chop the string as a list of "subkey, value" pairs, using ';' as a separator
                for (Subkey = Buffer; Subkey != NULL; Subkey = Nextkey)
                {
                    char *Value, *temp;
                    //  identify token pair boundary
                    Nextkey = (char*)strchr(Subkey, ';');
                    if (Nextkey)
                        *Nextkey++ = '\0';
                    // for each pair, split the subkey and the value
                    Value = (char*)strrchr(Subkey, ',');
                    if (Value == NULL)
                        return SynError(it8, "Invalid value for property '%s'.", VarName);
                    // gobble the spaces before the coma, and the coma itself
                    temp = Value++;
                    do *temp-- = '\0'; while (temp >= Subkey && *temp == ' ');
                    // gobble any space at the right
                    temp = Value + strlen(Value) - 1;
                    while (*temp == ' ') *temp-- = '\0';
                    // trim the strings from the left
                    Subkey += strspn(Subkey, " ");
                    Value += strspn(Value, " ");
                    if (Subkey[0] == 0 || Value[0] == 0)
                        return SynError(it8, "Invalid value for property '%s'.", VarName);
                    AddToList(it8, &GetTable(it8)->HeaderList, VarName, Subkey, Value, WRITE_PAIR);
                }
            }
            InSymbol(it8);
            break;
        case SEOLN: break;
        default:
                return SynError(it8, "expected keyword or identifier");
        }
    SkipEOLN(it8);
    }
    return TRUE;
}
static
void ReadType(cmsIT8* it8, char* SheetTypePtr)
{
    cmsInt32Number cnt = 0;
    // First line is a very special case.
    while (isseparator(it8->ch))
            NextCh(it8);
    while (it8->ch != '\r' && it8 ->ch != '\n' && it8->ch != '\t' && it8 -> ch != 0) {
        if (cnt++ < MAXSTR)
            *SheetTypePtr++= (char) it8 ->ch;
        NextCh(it8);
    }
    *SheetTypePtr = 0;
}
static
cmsBool ParseIT8(cmsIT8* it8, cmsBool nosheet)
{
    char* SheetTypePtr = it8 ->Tab[0].SheetType;
    if (nosheet == 0) {
        ReadType(it8, SheetTypePtr);
    }
    InSymbol(it8);
    SkipEOLN(it8);
    while (it8-> sy != SEOF &&
           it8-> sy != SSYNERROR) {
            switch (it8 -> sy) {
            case SBEGIN_DATA_FORMAT:
                    if (!DataFormatSection(it8)) return FALSE;
                    break;
            case SBEGIN_DATA:
                    if (!DataSection(it8)) return FALSE;
                    if (it8 -> sy != SEOF) {
                            AllocTable(it8);
                            it8 ->nTable = it8 ->TablesCount - 1;
                            // Read sheet type if present. We only support identifier and string.
                            // <ident> <eoln> is a type string
                            // anything else, is not a type string
                            if (nosheet == 0) {
                                if (it8 ->sy == SIDENT) {
                                    // May be a type sheet or may be a prop value statement. We cannot use insymbol in
                                    // this special case...
                                     while (isseparator(it8->ch))
                                         NextCh(it8);
                                     // If a newline is found, then this is a type string
                                    if (it8 ->ch == '\n' || it8->ch == '\r') {
                                         cmsIT8SetSheetType(it8, it8 ->id);
                                         InSymbol(it8);
                                    }
                                    else
                                    {
                                        // It is not. Just continue
                                        cmsIT8SetSheetType(it8, "");
                                    }
                                }
                                else
                                    // Validate quoted strings
                                    if (it8 ->sy == SSTRING) {
                                        cmsIT8SetSheetType(it8, it8 ->str);
                                        InSymbol(it8);
                                    }
                           }
                    }
                    break;
            case SEOLN:
                    SkipEOLN(it8);
                    break;
            default:
                    if (!HeaderSection(it8)) return FALSE;
           }
    }
    return (it8 -> sy != SSYNERROR);
}
// Init useful pointers
static
void CookPointers(cmsIT8* it8)
{
    int idField, i;
    char* Fld;
    cmsUInt32Number j;
    cmsUInt32Number nOldTable = it8 ->nTable;
    for (j=0; j < it8 ->TablesCount; j++) {
    TABLE* t = it8 ->Tab + j;
    t -> SampleID = 0;
    it8 ->nTable = j;
    for (idField = 0; idField < t -> nSamples; idField++)
    {
        if (t ->DataFormat == NULL){
            SynError(it8, "Undefined DATA_FORMAT");
            return;
        }
        Fld = t->DataFormat[idField];
        if (!Fld) continue;
        if (cmsstrcasecmp(Fld, "SAMPLE_ID") == 0) {
            t -> SampleID = idField;
            for (i=0; i < t -> nPatches; i++) {
                char *Data = GetData(it8, i, idField);
                if (Data) {
                    char Buffer[256];
                    strncpy(Buffer, Data, 255);
                    Buffer[255] = 0;
                    if (strlen(Buffer) <= strlen(Data))
                        strcpy(Data, Buffer);
                    else
                        SetData(it8, i, idField, Buffer);
                }
            }
        }
        // "LABEL" is an extension. It keeps references to forward tables
        if ((cmsstrcasecmp(Fld, "LABEL") == 0) || Fld[0] == '$' ) {
                    // Search for table references...
                    for (i=0; i < t -> nPatches; i++) {
                            char *Label = GetData(it8, i, idField);
                            if (Label) {
                                cmsUInt32Number k;
                                // This is the label, search for a table containing
                                // this property
                                for (k=0; k < it8 ->TablesCount; k++) {
                                    TABLE* Table = it8 ->Tab + k;
                                    KEYVALUE* p;
                                    if (IsAvailableOnList(Table->HeaderList, Label, NULL, &p)) {
                                        // Available, keep type and table
                                        char Buffer[256];
                                        char *Type  = p ->Value;
                                        int  nTable = (int) k;
                                        snprintf(Buffer, 255, "%s %d %s", Label, nTable, Type );
                                        SetData(it8, i, idField, Buffer);
                                    }
                                }
                            }
                    }
        }
    }
    }
    it8 ->nTable = nOldTable;
}
// Try to infere if the file is a CGATS/IT8 file at all. Read first line
// that should be something like some printable characters plus a \n
// returns 0 if this is not like a CGATS, or an integer otherwise. This integer is the number of words in first line?
static
int IsMyBlock(const cmsUInt8Number* Buffer, cmsUInt32Number n)
{
    int words = 1, space = 0, quot = 0;
    cmsUInt32Number i;
    if (n < 10) return 0;   // Too small
    if (n > 132)
        n = 132;
    for (i = 1; i < n; i++) {
        switch(Buffer[i])
        {
        case '\n':
        case '\r':
            return ((quot == 1) || (words > 2)) ? 0 : words;
        case '\t':
        case ' ':
            if(!quot && !space)
                space = 1;
            break;
        case '\"':
            quot = !quot;
            break;
        default:
            if (Buffer[i] < 32) return 0;
            if (Buffer[i] > 127) return 0;
            words += space;
            space = 0;
            break;
        }
    }
    return 0;
}
static
cmsBool IsMyFile(const char* FileName)
{
   FILE *fp;
   cmsUInt32Number Size;
   cmsUInt8Number Ptr[133];
   fp = fopen(FileName, "rt");
   if (!fp) {
       cmsSignalError(0, cmsERROR_FILE, "File '%s' not found", FileName);
       return FALSE;
   }
   Size = (cmsUInt32Number) fread(Ptr, 1, 132, fp);
   if (fclose(fp) != 0)
       return FALSE;
   Ptr[Size] = '\0';
   return IsMyBlock(Ptr, Size);
}
// ---------------------------------------------------------- Exported routines
cmsHANDLE  CMSEXPORT cmsIT8LoadFromMem(cmsContext ContextID, const void *Ptr, cmsUInt32Number len)
{
    cmsHANDLE hIT8;
    cmsIT8*  it8;
    int type;
    _cmsAssert(Ptr != NULL);
    _cmsAssert(len != 0);
    type = IsMyBlock((const cmsUInt8Number*)Ptr, len);
    if (type == 0) return NULL;
    hIT8 = cmsIT8Alloc(ContextID);
    if (!hIT8) return NULL;
    it8 = (cmsIT8*) hIT8;
    it8 ->MemoryBlock = (char*) _cmsMalloc(ContextID, len + 1);
    strncpy(it8 ->MemoryBlock, (const char*) Ptr, len);
    it8 ->MemoryBlock[len] = 0;
    strncpy(it8->FileStack[0]->FileName, "", cmsMAX_PATH-1);
    it8-> Source = it8 -> MemoryBlock;
    if (!ParseIT8(it8, type-1)) {
        cmsIT8Free(hIT8);
        return FALSE;
    }
    CookPointers(it8);
    it8 ->nTable = 0;
    _cmsFree(ContextID, it8->MemoryBlock);
    it8 -> MemoryBlock = NULL;
    return hIT8;
}
cmsHANDLE  CMSEXPORT cmsIT8LoadFromFile(cmsContext ContextID, const char* cFileName)
{
     cmsHANDLE hIT8;
     cmsIT8*  it8;
     int type;
     _cmsAssert(cFileName != NULL);
     type = IsMyFile(cFileName);
     if (type == 0) return NULL;
     hIT8 = cmsIT8Alloc(ContextID);
     it8 = (cmsIT8*) hIT8;
     if (!hIT8) return NULL;
     it8 ->FileStack[0]->Stream = fopen(cFileName, "rt");
     if (!it8 ->FileStack[0]->Stream) {
         cmsIT8Free(hIT8);
         return NULL;
     }
    strncpy(it8->FileStack[0]->FileName, cFileName, cmsMAX_PATH-1);
    it8->FileStack[0]->FileName[cmsMAX_PATH-1] = 0;
    if (!ParseIT8(it8, type-1)) {
            fclose(it8 ->FileStack[0]->Stream);
            cmsIT8Free(hIT8);
            return NULL;
    }
    CookPointers(it8);
    it8 ->nTable = 0;
    if (fclose(it8 ->FileStack[0]->Stream)!= 0) {
            cmsIT8Free(hIT8);
            return NULL;
    }
    return hIT8;
}
int CMSEXPORT cmsIT8EnumDataFormat(cmsHANDLE hIT8, char ***SampleNames)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    TABLE* t;
    _cmsAssert(hIT8 != NULL);
    t = GetTable(it8);
    if (SampleNames)
        *SampleNames = t -> DataFormat;
    return t -> nSamples;
}
cmsUInt32Number CMSEXPORT cmsIT8EnumProperties(cmsHANDLE hIT8, char ***PropertyNames)
{
    cmsIT8* it8 = (cmsIT8*) hIT8;
    KEYVALUE* p;
    cmsUInt32Number n;
    char **Props;
    TABLE* t;
    _cmsAssert(hIT8 != NULL);
    t = GetTable(it8);
    // Pass#1 - count properties
    n = 0;
/**代码未完, 请加载全部代码(NowJava.com).**/