/** @file  psMetadataIO.c
*
*  @brief Contains metadata input/output functions.
*
*  This file defines functions to read and write metadata to/from an external file.
*
*  @ingroup Metadata
*
*  @author Ross Harman, MHPCC
*  @author Eric Van Alst, MHPCC
*
*  @version $Revision: 1.30 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-06-10 18:09:12 $
*
*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
*/

#include <libxml/parser.h>
#include <fitsio.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#include "psAbort.h"
#include "psType.h"
#include "psMemory.h"
#include "psError.h"
#include "psString.h"
#include "psList.h"
#include "psHash.h"
#include "psVector.h"
#include "psMetadata.h"
#include "psMetadataIO.h"
#include "psConstants.h"
#include "psAstronomyErrors.h"

/******************************************************************************/
/*  DEFINE STATEMENTS                                                         */
/******************************************************************************/

/** Check for FITS errors */
#define FITS_ERROR(STRING,PS_ERROR)                                                                          \
fits_get_errstatus(status, fitsErr);                                                                         \
psError(PS_ERR_IO,true, STRING, PS_ERROR, fitsErr);                                                          \
status = 0;                                                                                                  \
fits_close_file(fd, &status);                                                                                \
if(status){                                                                                                  \
    fits_get_errstatus(status, fitsErr);                                                                     \
    psError(PS_ERR_IO,true, "Couldn't close FITS file. FITS error: %s", fitsErr);                            \
}                                                                                                            \
status = 0;                                                                                                  \
psFree(output);                                                                                              \
return NULL;

/** Maximum size of a FITS line */
#define FITS_LINE_SIZE 80

/** Maximum size of a string */
#define MAX_STRING_LENGTH 256


/******************************************************************************/
/*  TYPE DEFINITIONS                                                          */
/******************************************************************************/

// None

/*****************************************************************************/
/*  GLOBAL VARIABLES                                                         */
/*****************************************************************************/

// None

/*****************************************************************************/
/*  FILE STATIC VARIABLES                                                    */
/*****************************************************************************/

// None

/*****************************************************************************/
/*  FUNCTION IMPLEMENTATION - LOCAL                                          */
/*****************************************************************************/

static void saxEndElement(void *ctx, const xmlChar *tagName);
static void initVectorXml(void *ctx, char *tagName);
static void initMetadataItemXml(void *ctx, char *tagName);
static void saxStartElement(void *ctx, const xmlChar *tagName, const xmlChar **atts);
static psMetadata* getMetadataType(char *linePtr);
static psMetadata* setMetadataItem(psMetadata* template, char* linePtr)
;
static void parseLevelInfoFree(p_psParseLevelInfo* info);
static psBool parseLine(psS32* level,   psArray* levelArray,
                        char*  linePtr, psS32 lineCount,     char* fileName, psBool overwrite);
static psBool parseMetadataItem(char* keyName, psS32* level,     psArray* levelArray,
                                char* linePtr, psS32  lineCount, char*    fileName,    psMetadataFlags flags);

static void parseLevelInfoFree(p_psParseLevelInfo* info)
{
    psFree(info->nonUniqueKeyArray);
    psFree(info->typeArray);
    psFree(info->templateArray);
    psFree(info->name);
    psFree(info->metadata);
}

// Determines if a line is blank (whitespace only) or a commentline. It returns true if so. The input string
// must be null terminated.
psBool ignoreLine(char *inString)
{
    while(*inString!='\0' && *inString!='#') {
        if(!isspace(*inString)) {
            return false;
        }
        inString++;
    }

    return true;
}


//  Removes leading and trailing whitespace and # characters from a string. The cleaned string is a new null
//  terminated copy of the original input string.
char *cleanString(char *inString, psS32 sLen, psBool ignoreComment)
{
    char *ptrB = NULL;
    char *ptrE = NULL;
    char *cleaned = NULL;

    // Initialize begining of string pointer
    ptrB = inString;

    // Skip over leading # or whitespace
    if(ignoreComment) {
        while (isspace(*ptrB) || *ptrB=='#') {
            ptrB++;
        }
    } else {
        while (isspace(*ptrB)) {
            ptrB++;
        }
    }

    // Skip over trailing whitespace, null terminators, and # characters
    ptrE = inString + sLen;
    if(ignoreComment) {
        while(isspace(*ptrE) || *ptrE=='\0' || *ptrE=='#') {
            ptrE--;
        }
    } else {
        while(isspace(*ptrE) || *ptrE=='\0') {
            ptrE--;
        }
    }

    // Length, sLen, does not include '\0'
    sLen = ptrE - ptrB + 1;

    // Adds '\0' to end of string and +1 to sLen
    cleaned = psStringNCopy(ptrB, sLen);

    return cleaned;
}

// Count repeat occurances of a single character within a line. The input string must be null terminated.
psS32 repeatedChars(char *inString, char ch)
{
    psS32 count = 0;

    while(*inString!='\0') {
        if(*inString == ch) {
            count++;
        }
        inString++;
    }

    return count;
}

// Returns cleaned token based on delimiter, but not including delimiter. Also changes the pointer location
// the beginning of the string. Tokens are newly allocated null terminated strings.
char* getToken(char **inString, char *delimiter, psS32 *status, psBool ignoreComment)
{
    char *cleanToken = NULL;
    char *convertChar = NULL;
    psS32 sLen = 0;

    // Convert tab characters to white space
    while((convertChar=strchr(*inString,'\t')) != NULL ) {
        *convertChar = ' ';
    }

    // Skip over leading whitespace
    while(isspace(**inString)) {
        (*inString)++;
    }

    // Length of token, not including delimiter
    sLen = strcspn(*inString, delimiter);
    if(sLen) {

        // Create new, cleaned, and null terminated token
        cleanToken = cleanString(*inString, sLen,ignoreComment);

        // Move to end of token
        (*inString) += sLen;
    } else if(**inString!='\0' && sLen==0) {
        *status = 1;
    }

    return cleanToken;
}

// Returns single parsed value as a double precision number. The input string must be cleaned and null
// terminated.
double parseValue(char *inString, psS32 *status)
{
    char *end = NULL;
    double value = 0.0;

    value = strtod(inString, &end);
    if(*end != '\0') {
        *status = 1;
    } else if(inString==end) {
        *status = 1;
    }

    return value;
}

/** Returns true or false. 'T', 't', '1', 'F', 'f', and '0' are acceptable, parsable variations. */
psBool parseBool(char *inString, psS32 *status)
{
    psBool value = false;


    if(*inString=='T' || *inString=='t' || *inString=='1') {
        value = true;
    } else if(*inString=='F' || *inString=='f' || *inString=='0') {
        value = false;
    } else {
        *status = 1;
    }

    return value;
}

/** Returns parsed vector filled with with data. The input string must be null terminated. */
psVector* parseVector(char *inString, psElemType elemType, psS32 *status)
{
    char*      end       = NULL;
    char*      saveValue = NULL;
    psS32      i         = 0;
    psS32      numValues = 0;
    double     value     = 0.0;
    psVector*  vec       = NULL;

    // Cycle through string and count entries
    saveValue = inString;
    while(*inString!='\0') {
        strtod(inString, &end);
        if(inString==end) {
            *status = 1;
            return NULL;
        }
        while(*end==' ' || *end==',') { // Commas or spaces may be used as delimiters for vector values
            end++;
        }
        inString=end;
        numValues++;
    }

    // Cycle through string and convert string values to values
    if(numValues) {
        inString = saveValue;
        end = NULL;
        vec = psVectorAlloc(numValues, elemType);

        while(*inString!='\0') {
            value = strtod(inString, &end);
            if(inString==end) {
                *status = 1;
                return vec;
            }
            switch(elemType) {
            case PS_TYPE_U8:
                vec->data.U8[i++] = (psU8)value;
                break;
            case PS_TYPE_U16:
                vec->data.U16[i++] = (psU16)value;
                break;
            case PS_TYPE_U32:
                vec->data.U32[i++] = (psU32)value;
                break;
            case PS_TYPE_U64:
                vec->data.U64[i++] = (psU64)value;
                break;
            case PS_TYPE_S8:
                vec->data.S8[i++] = (psS8)value;
                break;
            case PS_TYPE_S16:
                vec->data.S16[i++] = (psS16)value;
                break;
            case PS_TYPE_S32:
                vec->data.S32[i++] = (psS32)value;
                break;
            case PS_TYPE_S64:
                vec->data.S64[i++] = (psS64)value;
                break;
            case PS_TYPE_F32:
                vec->data.F32[i++] = (psF32)value;
                break;
            case PS_TYPE_F64:
                vec->data.F64[i++] = (psF64)value;
                break;
            default:
                *status = 1;
                psError(PS_ERR_BAD_PARAMETER_VALUE,true,
                        PS_ERRORTEXT_psMetadataIO_TYPE_INVALID,
                        elemType);
            }

            while(*end==' ' || *end==',') {
                end++;
            }
            inString=end;
        }
    }

    return vec;
}

/*****************************************************************************/
/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
/*****************************************************************************/

p_psParseLevelInfo* p_psParseLevelInfoAlloc(void)
{

    p_psParseLevelInfo* info = NULL;

    // Allocate memory for parse level info
    info = (p_psParseLevelInfo*)psAlloc(sizeof(p_psParseLevelInfo));

    // If allocation successful then initialize members
    if(info != NULL) {

        info->nonUniqueKeyArray = psArrayAlloc(10);
        info->nonUniqueKeyArray->n = 0;

        info->typeArray = psArrayAlloc(10);
        info->typeArray->n = 0;

        info->templateArray = psArrayAlloc(10);
        info->templateArray->n = 0;

        info->metadata = NULL;
        info->name = NULL;

        // Set memory deallocator
        psMemSetDeallocator(info,(psFreeFcn)parseLevelInfoFree);
    }
    return info;
}

bool psMetadataItemPrint(FILE * fd, const char *format, const psMetadataItem* metadataItem)
{
    psMetadataType type;
    psBool success = true;

    PS_ASSERT_PTR_NON_NULL(fd, success);
    PS_ASSERT_PTR_NON_NULL(format, success);
    PS_ASSERT_PTR_NON_NULL(metadataItem, success);

    type = metadataItem->type;

    // determining the format type
    char* fType = strchr(format,'%');
    if (fType == NULL) {
        // well, the format contains no reference to the metadataItem's data:
        // that is truly trival to do!
        fprintf(fd,format);
        return success;
    }

    // skip over any format modifiers
    const char* formatEnd = format+strlen(format);
    while ( (fType < formatEnd) &&
        (strchr(" +-01234567890.$#, hlL",*(++fType)) != NULL) ) {}

    #define METADATAITEM_NUMERIC_CAST(FORMAT_TYPE) { \
        switch(type) { \
        case PS_META_BOOL: \
            fprintf(fd, format, (FORMAT_TYPE) metadataItem->data.B); \
            break; \
        case PS_META_S32: \
            fprintf(fd,format,(FORMAT_TYPE)  metadataItem->data.S32); \
            break; \
        case PS_META_F32: \
            fprintf(fd, format,(FORMAT_TYPE)  metadataItem->data.F32); \
            break; \
        case PS_META_F64: \
            fprintf(fd, format,(FORMAT_TYPE) metadataItem->data.F64); \
            break; \
        default: \
            psError(PS_ERR_BAD_PARAMETER_TYPE,true, \
                    PS_ERRORTEXT_psMetadata_METATYPE_INVALID, (int)type); \
            success = false; \
        } \
    }

    switch(*fType) {
    case 'd':
    case 'i':
    case 'c':
        METADATAITEM_NUMERIC_CAST(int)
        break;
    case 'o':
    case 'u':
    case 'x':
    case 'X':
        METADATAITEM_NUMERIC_CAST(unsigned int)
        break;
    case 'e':
    case 'E':
    case 'f':
    case 'F':
    case 'g':
    case 'G':
    case 'a':
    case 'A':
        METADATAITEM_NUMERIC_CAST(double)
        break;
    case 's':
        if (type == PS_META_STR) {
            fprintf(fd,format,(char*)metadataItem->data.V);
        } else {
            psError(PS_ERR_BAD_PARAMETER_TYPE,true,
                    PS_ERRORTEXT_psMetadata_METATYPE_INVALID, (int)type);
            success = false;
        }
        break;
    case 'p':
        fprintf(fd,format,metadataItem->data.V);
        break;
    default:
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                PS_ERRORTEXT_psMetadata_FORMAT_INVALID, *fType);
        break;
    }

    return success;
}


psMetadata* psMetadataReadHeader(psMetadata* output, char *extName, psS32 extNum, char *fileName)
{
    psBool tempBool;
    psBool success;
    char keyType;
    char keyName[FITS_LINE_SIZE];
    char keyValue[FITS_LINE_SIZE];
    char keyComment[FITS_LINE_SIZE];
    char fitsErr[MAX_STRING_LENGTH];
    psS32 i;
    psS32 hduType = 0;
    psS32 status = 0;
    psS32 numKeys = 0;
    psS32 keyNum = 0;
    fitsfile *fd = NULL;

    PS_ASSERT_PTR_NON_NULL(fileName,NULL);

    fits_open_file(&fd, fileName, READONLY, &status);
    if(fd == NULL || status != 0) {
        FITS_ERROR("FITS error while opening file: %s %s", fileName);
        return NULL;
    }

    if (extName == NULL && extNum < 1) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psMetadataIO_EXTNUM_NOTPOSITIVE,
                extNum);
        return NULL;
    }

    // Allocate metadata if user didn't
    if (output == NULL) {
        output = psMetadataAlloc();
    }

    // Move to user designated HDU number or HDU name in FITS file. HDU numbers starts at one.
    if (extName != NULL) {
        if (fits_movnam_hdu(fd, ANY_HDU, extName, 0, &status) != 0) {
            FITS_ERROR("FITS error while locating header %s: %s", extName);
        }
    } else {
        if (fits_movabs_hdu(fd, extNum, &hduType, &status) != 0) {
            FITS_ERROR("FITS error while locating header %d: %s", extNum);
        }
    }

    // Get number of key names
    if (fits_get_hdrpos(fd, &numKeys, &keyNum, &status) != 0) {
        FITS_ERROR("FITS error while reading key %d: %s", keyNum);
    }

    // Get each key name. Keywords start at one.
    for (i = 1; i <= numKeys; i++) {
        if (fits_read_keyn(fd, i, keyName, keyValue, keyComment, &status) != 0) {
            FITS_ERROR("FITS error while reading key %d: %s", keyNum);
        }
        if (fits_get_keytype(keyValue, &keyType, &status) != 0) {
            fits_get_errstatus(status, fitsErr);
            if (status != VALUE_UNDEFINED) {
                FITS_ERROR("FITS error while determining key %d type: %s", keyNum);
            } else {
                // Some keywords are still valid if they don't have a type (like COMMENTS and HISTORY)
                keyType = 'C';
                status = 0;
            }
        }

        switch (keyType) {
        case 'I':
            success = psMetadataAdd(output, PS_LIST_TAIL, keyName,
                                    PS_META_S32 | PS_META_DUPLICATE_OK,
                                    keyComment, atoi(keyValue));
            break;
        case 'F':
            success = psMetadataAdd(output, PS_LIST_TAIL, keyName,
                                    PS_META_F64 | PS_META_DUPLICATE_OK,
                                    keyComment, atof(keyValue));
            break;
        case 'C':
            success = psMetadataAdd(output, PS_LIST_TAIL, keyName,
                                    PS_META_STR | PS_META_DUPLICATE_OK,
                                    keyComment, keyValue);
            break;
        case 'L':
            tempBool = (keyValue[0] == 'T') ? 1 : 0;
            success = psMetadataAdd(output, PS_LIST_TAIL, keyName,
                                    PS_META_BOOL | PS_META_DUPLICATE_OK,
                                    keyComment, tempBool);
            break;
        case 'U':
        case 'X':
        default:
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FITS_METATYPE_INVALID, keyType);
            return output;
        }

        if (!success) {
            psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadataIO_ADD_FAILED, keyName);
            return output;
        }
    }

    return output;
}

static psMetadata* getMetadataType(char* linePtr)
{
    psMetadata*     metadataTemplate = NULL;
    psS32           status           = 0;
    char*           token            = NULL;
    psMetadataItem* tempItem         = NULL;

    // Loop through line and generate metadata items for each token found
    while((token = getToken(&linePtr," ",&status,false)) != NULL ) {

        // If not allocated then allocate new metadata
        if(metadataTemplate == NULL) {
            metadataTemplate = psMetadataAlloc();
        }

        // Look for comment indicator #
        if(strstr(token,"#") != 0) {
            psFree(token);
            break;
        }

        // Create metadata item to represent token read
        tempItem = psMetadataItemAllocStr(token,"","");
        if(tempItem == NULL) {
            psFree(metadataTemplate);
            psFree(token);
            metadataTemplate = NULL;
            break;
        }

        // Add item to template
        if(!psMetadataAddItem(metadataTemplate, tempItem, PS_LIST_TAIL, PS_META_DEFAULT)) {
            psFree(metadataTemplate);
            psFree(tempItem);
            psFree(token);
            metadataTemplate = NULL;
            break;
        }
        psFree(tempItem);
        psFree(token);
    }

    return metadataTemplate;
}

static psMetadata* setMetadataItem(psMetadata* template, char* linePtr)
{
    psMetadata*      md           = NULL;
    psS32            items        = 0;
    char*            token        = NULL;
    psS32            status       = 0;
    psMetadataItem*  mdItem       = NULL;
    psMetadataItem*  templateItem = NULL;
    psListIterator*  iter         = NULL;

    // Determine the number of items in template
    items = template->list->size;
    if(items > 0 ) {

        // Allocate metadata
        md = psMetadataAlloc()
             ;

        // Point to first item in template
        iter = psListIteratorAlloc(template->
                                   list,PS_LIST_HEAD,true);

        // For each item in template parse line string for values
        for(psS32 i = 0;
                i < items;
                i++) {

            // Get template item
            templateItem = psListGetAndIncrement(iter)
                           ;
            if(templateItem == NULL) {
                psFree(md);
                md = NULL;
                break;
            }

            // Get the next token on the line
            token = getToken(&linePtr," ",&status,false);
            if(token != NULL) {
                // Allocate metadata item
                mdItem = psMetadataItemAllocStr(templateItem->name,templateItem->comment,token);
                if(mdItem == NULL) {
                    psFree(md);
                    md = NULL;
                    psFree(token);
                    break;
                }
                // Add item to metadata
                if(!psMetadataAddItem(md, mdItem, PS_LIST_TAIL, PS_META_DEFAULT)) {
                    psFree(md);
                    md = NULL;
                    psFree(mdItem);
                    psFree(token);
                    break;
                }
                psFree(mdItem);
            } else {
                // Missing items
                psFree(md);
                md = NULL;
                break;
            }

            psFree(token);
        }
        psFree(iter);
    }
    return md;
}

psBool parseMetadataItem(char* keyName, psS32* level,     psArray* levelArray,
                         char* linePtr, psS32  lineCount, char*    fileName,   psMetadataFlags flags)
{
    psBool               returnValue   = true;
    psBool               addStatus     = false;
    psMetadataType       mdType        = PS_META_UNKNOWN;
    psElemType           vectorType    = PS_TYPE_S8;
    char*                strType       = NULL;
    char*                strValue      = NULL;
    char*                strComment    = NULL;
    psS32                status        = 0;
    psMetadata*          md            = NULL;
    psF64                tempDbl       = 0.0;
    psBool               tempBool      = false;
    psS32                tempInt       = 0;
    psVector*            tempVec       = NULL;
    char*                tempStr       = NULL;
    psArray*             nonUniqueKeys = NULL;
    psBool               typeFound     = false;
    psArray*             typeArray     = NULL;
    psArray*             templateArray = NULL;
    psMetadata*          tempMeta      = NULL;
    p_psParseLevelInfo*  nextLevelInfo = NULL;

    // Get the metadata item type
    strType = getToken(&linePtr, " ", &status,true);

    // Check for no type
    if(strType==NULL) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "type",lineCount,
                fileName);
        returnValue = false;
    } else {

        // Set metadata type based on type token

        // Check if the keyName specifies a vector and if so use strType token to find vector type
        if(*keyName == '@') {
            mdType = PS_META_VEC;
            // Get the type of vector
            if(!strncmp(strType, "U8", 2)) {
                vectorType = PS_TYPE_U8;
            } else if (!strncmp(strType,"U16",3)) {
                vectorType = PS_TYPE_U16;
            } else if (!strncmp(strType,"U32",3)) {
                vectorType = PS_TYPE_U32;
            } else if (!strncmp(strType,"U64",3)) {
                vectorType = PS_TYPE_U64;
            } else if (!strncmp(strType,"S8",2)) {
                vectorType = PS_TYPE_S8;
            } else if (!strncmp(strType,"S16",3)) {
                vectorType = PS_TYPE_S16;
            } else if (!strncmp(strType,"S32",3)) {
                vectorType = PS_TYPE_S32;
            } else if (!strncmp(strType,"S64",3)) {
                vectorType = PS_TYPE_S64;
            } else if (!strncmp(strType,"F32",3)) {
                vectorType = PS_TYPE_F32;
            } else if (!strncmp(strType,"F64",3)) {
                vectorType = PS_TYPE_F64;
            } else {
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, "", keyName,
                        strType, lineCount, fileName);
                psFree(strType);
                return false;
            }
        } else if(!strncmp(strType, "STR", 3)) {
            mdType = PS_META_STR;
        } else if(!strncmp(strType, "BOOL", 4)) {
            mdType = PS_META_BOOL;
        } else if(!strncmp(strType, "S32", 3)) {
            mdType = PS_META_S32;
        } else if(!strncmp(strType, "F32", 3)) {
            mdType = PS_META_F32;
        } else if(!strncmp(strType, "F64", 3)) {
            mdType = PS_META_F64;
        } else if(!strncmp(strType, "MULTI", 5)) {
            mdType = PS_META_MULTI;
        } else if(!strncmp(strType, "METADATA", 8)) {
            mdType = PS_META_META;
        } else {
            // Search through user types
            typeArray = ((p_psParseLevelInfo*)(levelArray->data[*level]))->typeArray;
            templateArray = ((p_psParseLevelInfo*)(levelArray->data[*level]))->templateArray;
            for(psS32 k = 0; k < typeArray->n; k++) {
                if(strcmp(strType,(char*)typeArray->data[k]) == 0) {
                    tempMeta = setMetadataItem((psMetadata*)templateArray->data[k],linePtr);
                    if(tempMeta != NULL) {
                        // Add metadata item
                        md = ((p_psParseLevelInfo*)(levelArray->data[*level]))->metadata;
                        addStatus = psMetadataAdd(md,PS_LIST_TAIL,keyName,PS_META_META | flags,"",tempMeta);
                        // Check for add failure
                        if (! addStatus) {
                            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_OVERWRITE_ITEM,
                                    keyName, lineCount, fileName);
                            psFree(strType);
                            psFree(tempMeta);
                            return false;
                        }
                        psFree(tempMeta);
                    } else {
                        // Metadata type read error
                        psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL,
                                keyName,lineCount,fileName);
                        psFree(strType);
                        return false;
                    }
                    typeFound = true;
                    break;
                }
            }
            if(!typeFound) {
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID,
                        strType, lineCount,fileName);
                psFree(strType);
                return false;
            } else {
                psFree(strType);
                return true;
            }
        }
    }

    // If type is not MULTI or META then get the value and comment
    if((mdType != PS_META_MULTI) && (mdType != PS_META_META)) {
        // Get the metadata item value if there is one.
        status = 0;
        strValue = getToken(&linePtr, "#", &status,true);
        if(status) {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "value", lineCount,
                    fileName);
            psFree(strType);
            psFree(strValue);
            return false;
        }
        if(strValue==NULL) {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "value", lineCount,
                    fileName);
            psFree(strType);
            psFree(strValue);
            return false;
        }
        // Not all lines will have comments, so NULL is ok.
        status = 0;
        strComment = getToken(&linePtr,"~", &status,true);
        if(status) {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "comment", lineCount,
                    fileName);
            psFree(strType);
            psFree(strValue);
            psFree(strComment);
        }
    }

    // Need to add item to metadata so get pointer to metadata
    status = 0;
    md = ((p_psParseLevelInfo*)(levelArray->data[*level]))->metadata;
    nonUniqueKeys = ((p_psParseLevelInfo*)(levelArray->data[*level]))->nonUniqueKeyArray;
    switch(mdType) {
    case PS_META_STR:
        addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                  mdType | flags,
                                  strComment, strValue);
        break;
    case PS_META_BOOL:
        tempBool = parseBool(strValue, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                      mdType | flags,
                                      strComment, tempBool);
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName,
                    strType, lineCount, fileName);
            returnValue = false;
        }
        break;
    case PS_META_F32:
    case PS_META_F64:
        tempDbl = parseValue(strValue, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                      mdType | flags,
                                      strComment, tempDbl);
        } else {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName, strType, lineCount,
                    fileName);
            returnValue = false;
        }
        break;
    case PS_META_S32:
        tempInt = (psS32)parseValue(strValue, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                      mdType | flags,
                                      strComment, tempInt);
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName,
                    strType, lineCount, fileName);
            returnValue = false;
        }
        break;
    case PS_META_VEC:
        tempVec = parseVector(strValue, vectorType, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName+1,
                                      mdType | flags,
                                      strComment, tempVec);
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName,
                    strType, lineCount, fileName);
            returnValue = false;
        }
        psFree(tempVec);
        break;
    case PS_META_MULTI:
        // Add key to non-unique array of keys
        // Check for duplicate MULTI lines
        addStatus = true;
        for(psS32 k=0; k < nonUniqueKeys->n; k++) {
            if(strcmp(keyName,(char*)nonUniqueKeys->data[k]) == 0) {
                psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_DUPLICATE_MULTI,
                        lineCount,fileName);
                psFree(strType);
                return false;
            }
        }
        tempStr = psStringCopy(keyName);
        nonUniqueKeys = psArrayAdd(nonUniqueKeys,0,tempStr);
        addStatus = true;
        psFree(tempStr);
        break;
    case PS_META_META:
        // Create next level info
        nextLevelInfo = p_psParseLevelInfoAlloc();
        // Create new metadata
        nextLevelInfo->metadata = psMetadataAlloc();
        // Save name of metadata
        nextLevelInfo->name = psStringCopy(keyName);
        // Add next level to levelArray
        levelArray = psArrayAdd(levelArray,1,nextLevelInfo);
        psFree(nextLevelInfo);
        // Increment level counter
        (*level)++;
        addStatus = true;
        break;
    default:
        psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID,
                lineCount,fileName);
        break;
    }

    // Check if the add status was successful
    if (! addStatus) {
        //        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_OVERWRITE_ITEM, keyName, lineCount,
        //                fileName);
        returnValue = false;
    }

    psFree(strComment);
    psFree(strValue);
    psFree(strType);

    return returnValue;
}

psBool parseLine(psS32* level,     psArray* levelArray, char*  linePtr,
                 psS32  lineCount, char*    fileName,   psBool overwrite)
{
    psBool              returnValue    = true;
    psMetadataFlags     flags          = PS_META_DEFAULT;
    char*               keyName        = NULL;
    psS32               status         = 0;
    psS32               limit          = 0;
    psMetadata*         tempTemplate   = NULL;
    char*               strType        = NULL;
    psArray*            typeArray      = NULL;
    psArray*            templateArray  = NULL;
    p_psParseLevelInfo* upperLevelInfo = NULL;
    p_psParseLevelInfo* lowerLevelInfo = NULL;
    psBool              addStatus      = 0;

    // Set flags if overwrite specified
    if(overwrite) {
        flags = PS_META_REPLACE;
    }

    // If line is not a comment or blank, then extract data
    if(!ignoreLine(linePtr)) {

        // Check for more than one '@' in a line
        if(repeatedChars(linePtr, '@') > 1) {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psMetadataIO_FILE_MULTIPLE_CHAR, '@', lineCount, fileName);
            return false;
        }

        // Get metadata item name
        keyName = getToken(&linePtr, " ", &status,true);
        if(status) {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "keyName", lineCount, fileName);
            psFree(keyName);
            return false;
        }

        // Check for special keyName values "TYPE", "END"
        if(strcmp(keyName,"TYPE") == 0 ) {
            // Get the type name
            strType = getToken(&linePtr," ",&status,true);
            if(strType == NULL) {
                psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL,"type",lineCount,
                        fileName);
                psFree(keyName);
                return false;
            }
            tempTemplate = getMetadataType(linePtr);
            // Check if type was parsed succesfully
            if(tempTemplate != NULL) {
                // Access type array
                typeArray = ((p_psParseLevelInfo*)(levelArray->data[*level]))->typeArray;
                // Check if type already exists in array
                for(psS32 k=0; k < typeArray->n; k++) {
                    // Compare type name with the list of current types
                    if(strcmp(strType,(char*)typeArray->data[k]) == 0) {
                        psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_TYPE_DUPLICATE,
                                strType,lineCount,fileName);
                        psFree(tempTemplate);
                        psFree(keyName);
                        psFree(strType);
                        return false;
                    }
                }
                // Add key name to array of type
                typeArray = psArrayAdd(typeArray,1,strType);
                // Add template to array of templates
                templateArray = ((p_psParseLevelInfo*)(levelArray->data[*level]))->templateArray;
                templateArray = psArrayAdd(templateArray,1,tempTemplate);
                psFree(tempTemplate);
            } else {
                psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID,
                        strType,lineCount,fileName);
                psFree(keyName);
                psFree(strType);
                return false;
            }
            psFree(strType);
        } else if (strcmp(keyName,"END") == 0 ) {
            if(*level > 0) {
                upperLevelInfo = ((p_psParseLevelInfo*)(levelArray->data[*level-1]));
                lowerLevelInfo = ((p_psParseLevelInfo*)(levelArray->data[*level]));
                // Check name in nonunique list
                for(psS32 k=0; k < upperLevelInfo->nonUniqueKeyArray->n; k++) {
                    if(strcmp(upperLevelInfo->nonUniqueKeyArray->data[k],lowerLevelInfo->name) == 0) {
                        flags = PS_META_DUPLICATE_OK;
                        break;
                    }
                }
                // Add metadata to upper level metadata
                addStatus = psMetadataAdd(upperLevelInfo->metadata,
                                          PS_LIST_TAIL,lowerLevelInfo->name,
                                          PS_META_META | flags,
                                          "",
                                          lowerLevelInfo->metadata);
                if(!addStatus) {
                    psFree(keyName);
                    return false;
                } else {
                    // Remove lower info level
                    if(!psArrayRemove(levelArray,levelArray->data[*level])) {
                        psFree(keyName);
                        return false;
                    }
                    psFree(lowerLevelInfo);
                    (*level)--;
                }
            } else {
                psFree(keyName);
                return false;
            }
        } else {
            // Check if key name present in array of non-unique key names
            limit = ((p_psParseLevelInfo*)(levelArray->data[*level]))->nonUniqueKeyArray->n;
            for(psS32 k=0; k < limit; k++) {
                char* name = (char*)((p_psParseLevelInfo*)
                                     (levelArray->data[*level]))->nonUniqueKeyArray->data[k];
                if(strcmp(name,keyName) == 0) {
                    flags = PS_META_DUPLICATE_OK;
                }
            }
            // Parse metadataItem
            if(!parseMetadataItem(keyName,level, levelArray, linePtr, lineCount, fileName, flags)) {
                psFree(keyName);
                return false;
            }
        }
        psFree(keyName);
    }
    return returnValue;
}

psMetadata* psMetadataConfigParse(psMetadata* md, psU32 *nFail, const char *fileName, psBool overwrite)
{
    FILE*               fp                   = NULL;
    char*               line                 = NULL;
    char*               linePtr              = NULL;
    psArray*            parseLevelInfoArray  = NULL;
    psS32               lineCount            = 0;
    psS32               nestingLevel         = 0;
    p_psParseLevelInfo* topLevelInfo         = NULL;

    // Check for NULL file name
    PS_ASSERT_PTR_NON_NULL(fileName,NULL);

    // Check for NULL nFail
    PS_ASSERT_PTR_NON_NULL(nFail,NULL);

    // Attempt to open specified file
    if((fp=fopen(fileName, "r")) == NULL) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_OPEN_FAILED, fileName);
        return NULL;
    }

    // Allocate metadata if necessary
    if (md == NULL) {
        md = psMetadataAlloc();
    }

    // Allocate array to store parse level information
    parseLevelInfoArray = psArrayAlloc(10);
    parseLevelInfoArray->n = 0;

    // Set parse level info for the top level
    topLevelInfo = p_psParseLevelInfoAlloc();
    topLevelInfo->metadata = psMemIncrRefCounter(md);
    parseLevelInfoArray = psArrayAdd(parseLevelInfoArray,1,topLevelInfo);
    psFree(topLevelInfo);

    // Create reusable line for continuous read
    line = (char*)psAlloc(MAX_STRING_LENGTH*sizeof(char));

    // While loop to parse the file
    while(fgets(line, MAX_STRING_LENGTH, fp) != NULL) {

        // Initialize variables for new line
        linePtr = line;
        lineCount++;

        if(!parseLine(&nestingLevel,parseLevelInfoArray,linePtr,lineCount,(char*)fileName,overwrite)) {
            (*nFail)++;
        }
    }

    // Free parse array and line buffer
    psFree(parseLevelInfoArray);
    psFree(line);

    return md;
}


static void saxStartElement(void *ctx, const xmlChar *tagName, const xmlChar **atts)
{
    psU64 i = 0;
    char* psTagName = NULL;
    char *psAttName = NULL;
    char *psAttValue = NULL;
    const xmlChar *attName = NULL;
    const xmlChar *attValue = NULL;
    psMetadata* md = NULL;
    psHash* htAtts = NULL;
    xmlParserCtxtPtr ctxt = NULL;
    xmlParserInputPtr input = NULL;


    // Get and check initial data pointers
    ctxt = (xmlParserCtxtPtr)ctx;
    PS_ASSERT_GENERAL_PTR_NON_NULL(ctxt, return);
    md = (psMetadata*)ctxt->sax->_private;
    PS_ASSERT_GENERAL_PTR_NON_NULL(md, return);
    input = (xmlParserInputPtr)ctxt->input;
    PS_ASSERT_GENERAL_PTR_NON_NULL(input, return);

    // Copy XML strings to psStrings to avoid libxml2/psLib memory corruption problems
    psTagName = psStringCopy((const char*)tagName);

    // Metadata containter for housing element attributes used by other SAX events
    htAtts = psHashAlloc(10);


    // Get tag name
    if(psTagName != NULL) {
        psHashAdd(htAtts, "tagName", psTagName);
    } else {
        PS_ASSERT_GENERAL_PTR_NON_NULL(psTagName, return);
        psFree(htAtts);
        psFree(psTagName);
        return;
    }

    // Get all attribute names and attribute values
    if(atts != NULL) {
        attName = atts[i++];
        attValue = atts[i++];
        while(attName != NULL) {
            if(attValue != NULL) {

                // Copy XML strings to psStrings to avoid libxml2/psLib memory corruption problems
                psAttName = psStringCopy((const char*)attName);
                psAttValue = psStringCopy((const char*)attValue);
                psHashAdd(htAtts, psAttName, psAttValue);
                psFree(psAttName);
                psFree(psAttValue);
            } else {
                PS_ASSERT_GENERAL_PTR_NON_NULL(psAttValue, return);
                psFree(htAtts);
                psFree(psTagName);
                return;
            }
            attName = atts[i++];
            attValue = atts[i++];
        }
    }

    // Add attributes to metadata

    psMetadataAdd(md, PS_LIST_TAIL, "htAtts",
                  PS_META_HASH | PS_META_DUPLICATE_OK,
                  NULL, htAtts);

    psFree(psTagName);
    psFree(htAtts);

    return;
}

static void initMetadataItemXml(void *ctx, char *tagName)
{
    psBool overwrite = false;
    psBool tempBool = false;
    psS32 status = 0;
    psU32 lineNumber = 0;
    psF64 tempDbl = 0.0;
    psS32 tempInt = 0.0;
    psMetadataType mdType = PS_META_UNKNOWN;
    char *fileName = NULL;
    char *strName = NULL;
    char *strType = NULL;
    char *strValue = NULL;
    psMetadata* md = NULL;
    psHash* htAtts = NULL;
    psMetadataItem *metadataItem = NULL;
    xmlParserCtxtPtr ctxt = NULL;
    xmlParserInputPtr input = NULL;


    // Get and check initial data pointers
    ctxt = (xmlParserCtxtPtr)ctx;
    PS_ASSERT_GENERAL_PTR_NON_NULL(ctxt, return);
    md = (psMetadata*)ctxt->sax->_private;
    PS_ASSERT_GENERAL_PTR_NON_NULL(md, return);
    input = (xmlParserInputPtr)ctxt->input;
    PS_ASSERT_GENERAL_PTR_NON_NULL(input, return);
    metadataItem = psMetadataLookup(md, "htAtts");
    PS_ASSERT_GENERAL_PTR_NON_NULL(metadataItem, return);
    PS_ASSERT_GENERAL_PTR_NON_NULL(metadataItem->data.list, return);
    metadataItem = (psMetadataItem*)psListGet(metadataItem->data.list,PS_LIST_TAIL);
    htAtts = (psHash*)metadataItem->data.list;
    PS_ASSERT_GENERAL_PTR_NON_NULL(htAtts, return);
    fileName = (char*)input->filename;
    PS_ASSERT_GENERAL_PTR_NON_NULL(fileName, return);
    lineNumber = input->line;

    // Get attribute name
    strName = psHashLookup(htAtts, "name");
    if(strName == NULL) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_NO_NAME, lineNumber, fileName);
        return;
    }

    // Get attribute type, if there is one
    strType = psHashLookup(htAtts, "psType");
    if(strType!= NULL) {
        if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psString")) {
            mdType = PS_META_STR;
        } else if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psBool")) {
            mdType = PS_META_BOOL;
        } else if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psS32")) {
            mdType = PS_META_S32;
        } else if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psF32")) {
            mdType = PS_META_F32;
        } else if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psF64")) {
            mdType = PS_META_F64;
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_TYPE_INVALID_LINE_FILE, strType, lineNumber,
                    fileName);
            return;
        }
    }

    // Get attribute value, if there is one
    strValue = psHashLookup(htAtts, "value");

    /* If metadata item is found, and is not a folder node, and overwrite is allowed, then remove
    existing and allow switch/case below to add new item. If overwrite is false, then report error. If
    found item is folder node, then psMetadataAdd will automatically add a new child. */
    metadataItem = psMetadataLookup(md, "overwrite");
    if(metadataItem != NULL) {
        overwrite = parseBool((char*)strValue, &status);
        if(status) {
            status = 0;
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName, strType,
                    lineNumber, fileName);
        }
        metadataItem = psMetadataLookup(md, strName);
        if(metadataItem != NULL) {
            if(metadataItem->type != PS_META_LIST) {
                if(overwrite) {
                    psMetadataRemove(md, INT_MIN, strName);
                } else {
                    psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_OVERWRITE_ITEM, strName, lineNumber,
                            fileName);
                    return;
                }
            }
        }
    }

    // Create metadata item and add to metadata
    switch(mdType) {
    case PS_META_LIST:
        psMetadataAdd(md, PS_LIST_TAIL, strName,
                      mdType | PS_META_DUPLICATE_OK,
                      NULL, NULL);
        break;
    case PS_META_STR:
        psMetadataAdd(md, PS_LIST_TAIL, strName,
                      mdType | PS_META_DUPLICATE_OK,
                      NULL, strValue);
        break;
    case PS_META_BOOL:
        tempBool = parseBool((char*)strValue, &status);
        if(!status) {
            psMetadataAdd(md, PS_LIST_TAIL, strName,
                          mdType | PS_META_DUPLICATE_OK,
                          NULL, tempBool);
        } else {
            status = 0;
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName, strType,
                    lineNumber, fileName);
        }
        break;
    case PS_META_S32:
        tempInt = (psS32)parseValue((char*)strValue, &status);
        if(!status) {
            psMetadataAdd(md, PS_LIST_TAIL, strName,
                          mdType | PS_META_DUPLICATE_OK,
                          NULL, tempInt);
        } else {
            status = 0;
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName, strType,
                    lineNumber, fileName);
        }
        break;
    case PS_META_F32:
    case PS_META_F64:
        tempDbl = parseValue((char*)strValue, &status);
        if(!status) {
            psMetadataAdd(md, PS_LIST_TAIL, strName,
                          mdType | PS_META_DUPLICATE_OK,
                          NULL, tempDbl);
        } else {
            status = 0;
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName, strType,
                    lineNumber, fileName);
        }
        break;
    default:
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID, strType, lineNumber, fileName);
    } // End switch

    return;
}


static void initVectorXml(void *ctx, char *tagName)
{
    bool overwrite = false;
    psS32 status = 0;
    psU32 lineNumber = 0;
    psElemType pType = 0;
    char *strName = NULL;
    char *strType = NULL;
    char *strValue = NULL;
    char *fileName = NULL;
    psMetadataItem *table = NULL;
    psMetadataItem *tables = NULL;
    psMetadataItem *metadataItem = NULL;
    psVector *vec = NULL;
    psMetadata* md = NULL;
    psHash* htAtts = NULL;
    xmlParserCtxtPtr ctxt = NULL;
    xmlParserInputPtr input = NULL;


    // Get and check initial data pointers
    ctxt = (xmlParserCtxtPtr)ctx;
    PS_ASSERT_GENERAL_PTR_NON_NULL(ctxt, return);
    md = (psMetadata*)ctxt->sax->_private;
    PS_ASSERT_GENERAL_PTR_NON_NULL(md, return);
    input = (xmlParserInputPtr)ctxt->input;
    PS_ASSERT_GENERAL_PTR_NON_NULL(input, return);
    tables = psMetadataLookup(md, "htAtts");
    PS_ASSERT_GENERAL_PTR_NON_NULL(tables, return);
    PS_ASSERT_GENERAL_PTR_NON_NULL(tables->data.list, return);
    table = (psMetadataItem*)psListGet(tables->data.list,PS_LIST_TAIL);
    htAtts = (psHash*)table->data.list;
    PS_ASSERT_GENERAL_PTR_NON_NULL(htAtts, return);
    fileName = (char*)input->filename;
    PS_ASSERT_GENERAL_PTR_NON_NULL(fileName, return);
    lineNumber = input->line;

    // Get attribute name
    strName = psHashLookup(htAtts, "name");
    if(strName == NULL) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_NO_NAME, lineNumber, fileName);
        return;
    }

    // Get attribute type, if there is one
    strType = psHashLookup(htAtts, "psType");
    if(strType!= NULL) {
        if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psBool")) {
            pType = PS_TYPE_U8;
        } else if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psS32")) {
            pType = PS_TYPE_S32;
        } else if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psF32")) {
            pType = PS_TYPE_F32;
        } else if(xmlStrEqual(BAD_CAST strType, BAD_CAST "psF64")) {
            pType = PS_TYPE_F64;
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_TYPE_INVALID_LINE_FILE, strName, lineNumber,
                    fileName);
            return;
        }
    }

    strValue = psHashLookup(htAtts, "value");
    PS_ASSERT_GENERAL_PTR_NON_NULL(strValue, return);


    /* If metadata item is found, and is not a folder node, and overwrite is allowed, then remove
    existing and allow switch/case below to add new item. If overwrite is false, then report error. If
    found item is folder node, then psMetadataAdd will automatically add a new child. */
    metadataItem = psMetadataLookup(md, "overwrite");
    if(metadataItem != NULL) {
        overwrite = parseBool((char*)strValue, &status);
        if(status) {
            status = 0;
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName, strType,
                    input->line, input->filename);
        }
        metadataItem = psMetadataLookup(md, strName);
        if(metadataItem != NULL) {
            if(metadataItem->type != PS_META_LIST) {
                if(overwrite) {
                    psMetadataRemove(md, INT_MIN, strName);
                } else {
                    psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_OVERWRITE_ITEM, strName, lineNumber,
                            fileName);
                    return;
                }
            }
        }
    }

    // Get value
    vec = parseVector((char*)strValue, pType, &status);
    if(!status) {
        psMetadataAdd(md, PS_LIST_TAIL, strName+1,
                      PS_META_VEC | PS_META_DUPLICATE_OK,
                      NULL, vec);
    } else {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName, strType,
                lineNumber, fileName);
    }
    psFree(vec);
}

static void saxEndElement(void *ctx, const xmlChar *tagName)
{
    char *psStartTagName = NULL;
    char *psEndTagName = NULL;
    psMetadata* md = NULL;
    psHash* htAtts = NULL;
    psMetadataItem *table = NULL;
    psMetadataItem *tables = NULL;
    xmlParserCtxtPtr ctxt = NULL;
    xmlParserInputPtr input = NULL;


    // Get and check initial data pointers
    ctxt = (xmlParserCtxtPtr)ctx;
    PS_ASSERT_GENERAL_PTR_NON_NULL(ctxt, return);
    md = (psMetadata*)ctxt->sax->_private;
    PS_ASSERT_GENERAL_PTR_NON_NULL(md, return);
    input = (xmlParserInputPtr)ctxt->input;
    PS_ASSERT_GENERAL_PTR_NON_NULL(input, return);
    tables = psMetadataLookup(md, "htAtts");
    PS_ASSERT_GENERAL_PTR_NON_NULL(tables, return);
    PS_ASSERT_GENERAL_PTR_NON_NULL(tables->data.list, return);
    table = (psMetadataItem*)psListGet(tables->data.list,PS_LIST_TAIL);
    htAtts = (psHash*)table->data.list;
    PS_ASSERT_GENERAL_PTR_NON_NULL(htAtts, return);

    // Copy XML strings to psStrings to avoid libxml2/psLib memory corruption problems
    psEndTagName = psStringCopy((const char*)tagName);

    // Compare start and end tag names
    psStartTagName = psHashLookup(htAtts, "tagName");
    PS_ASSERT_GENERAL_PTR_NON_NULL(psStartTagName, return);
    if(strcmp(psEndTagName, psStartTagName)) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_TAG_MISMATCH, psStartTagName, psEndTagName);
    }

    // Initialize psLib structs
    if(!strcmp(psEndTagName, "psMetadataItem")) {
        initMetadataItemXml(ctx, psEndTagName);
    } else if(!strcmp(psEndTagName, "psVector")) {
        initVectorXml(ctx, psEndTagName);
    } else if(strcmp(psEndTagName, "psRoot")) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_TAG_UNKNOWN, psEndTagName);
    }

    // Free temporary metadata item and its hash table
    psListRemove(tables->data.list, PS_LIST_TAIL);

    psFree(psEndTagName);

    return;
}

psMetadata*  psMetadataConfigParseXml(psMetadata* md, psU32 *nFail, const char *fileName, psBool overwrite)
{
    xmlSAXHandler saxHandler;


    // Error checks
    PS_ASSERT_PTR_NON_NULL(fileName, NULL);

    // Allocate metadata if necessary
    if (md == NULL) {
        md = psMetadataAlloc();
    }

    // Sax handler initializations
    saxHandler.internalSubset           = NULL;
    saxHandler.isStandalone             = NULL;
    saxHandler.hasInternalSubset        = NULL;
    saxHandler.hasExternalSubset        = NULL;
    saxHandler.resolveEntity            = NULL;
    saxHandler.getEntity                = NULL;
    saxHandler.entityDecl               = NULL;
    saxHandler.notationDecl             = NULL;
    saxHandler.attributeDecl            = NULL;
    saxHandler.elementDecl              = NULL;
    saxHandler.unparsedEntityDecl       = NULL;
    saxHandler.setDocumentLocator       = NULL;
    saxHandler.startDocument            = NULL;
    saxHandler.endDocument              = NULL;
    saxHandler.startElement             = saxStartElement;
    saxHandler.endElement               = saxEndElement;
    saxHandler.reference                = NULL;
    saxHandler.characters               = NULL;
    saxHandler.ignorableWhitespace      = NULL;
    saxHandler.processingInstruction    = NULL;
    saxHandler.comment                  = NULL;
    saxHandler.warning                  = xmlParserError;
    saxHandler.error                    = xmlParserError;
    saxHandler.fatalError               = xmlParserError;
    saxHandler.getParameterEntity       = NULL;
    saxHandler.cdataBlock               = NULL;
    saxHandler.externalSubset           = NULL;
    saxHandler.initialized              = 1;
    saxHandler._private                 = md;
    saxHandler.startElementNs           = NULL;
    saxHandler.endElementNs             = NULL;
    saxHandler.serror                   = NULL;

    // Parse XML file
    if (xmlSAXUserParseFile(&saxHandler, NULL, fileName)) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_OPEN_FAILED, fileName);
        return NULL;
    }

    // Parser and memory cleanups for libxml2
    xmlCleanupParser();
    xmlMemoryDump();

    return md;
}
