/** @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
*
*  @version $Revision: 1.23 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-03-17 19:01:01 $
*
*  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;

/** Free and null temporary variables used by config file parser */
#define CLEAR_TEMPS()                                                                                        \
if(strName) {                                                                                                \
    psFree(strName);                                                                                         \
    strName = NULL;                                                                                          \
}                                                                                                            \
if(strType) {                                                                                                \
    psFree(strType);                                                                                         \
    strType = NULL;                                                                                          \
}                                                                                                            \
if(strValue) {                                                                                               \
    psFree(strValue);                                                                                        \
    strValue = NULL;                                                                                         \
}                                                                                                            \
if(strComment) {                                                                                             \
    psFree(strComment);                                                                                      \
    strComment = 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);

/** 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)
{
    char *ptrB = NULL;
    char *ptrE = NULL;
    char *cleaned = NULL;


    ptrB = inString;

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

    /* Skip over trailing whitespace, null terminators, and # characters */
    ptrE = inString + sLen;
    while(isspace(*ptrE) || *ptrE=='\0' || *ptrE=='#') {
        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)
{
    char *cleanToken = NULL;
    psS32 sLen = 0;


    // 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);

        // 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_S32:
                vec->data.S32[i++] = (psS32)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                                          */
/*****************************************************************************/

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

    PS_PTR_CHECK_NULL(fd, success);
    PS_PTR_CHECK_NULL(format, success);
    PS_PTR_CHECK_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_PTR_CHECK_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;
}

psMetadata* psMetadataParseConfig(psMetadata* md, psU32 *nFail, const char *fileName, psBool overwrite)
{
    psBool tempBool;
    char *line = NULL;
    char *strName = NULL;
    char *strType = NULL;
    char *strValue = NULL;
    char *strComment = NULL;
    char *linePtr = NULL;
    psElemType vecType = PS_TYPE_PTR;
    psS32 status = 0;
    psU32 lineCount = 0;
    psF64 tempDbl = 0.0;
    psS32 tempInt = 0.0;
    psVector *tempVec = NULL;
    FILE *fp = NULL;
    psMetadataType mdType;
    psMetadataFlags flags;
    psBool addStatus;

    // Check for nulls
    PS_PTR_CHECK_NULL(fileName,NULL);
    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();
    }

    // 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++;
        CLEAR_TEMPS();

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

            // Check for more than one '*' or '@' in a line
            if(repeatedChars(linePtr, '@') > 1) {
                (*nFail)++;
                psError(PS_ERR_IO, true,
                        PS_ERRORTEXT_psMetadataIO_FILE_MULTIPLE_CHAR, '@', lineCount, fileName);
                continue;
            } else if(repeatedChars(linePtr, '*') > 1) {
                (*nFail)++;
                psError(PS_ERR_IO, true,
                        PS_ERRORTEXT_psMetadataIO_FILE_MULTIPLE_CHAR, '*', lineCount, fileName);
                continue;
            } else if(repeatedChars(linePtr, '~') > 0) {
                (*nFail)++;
                psError(PS_ERR_IO, true,
                        PS_ERRORTEXT_psMetadataIO_FILE_MULTIPLE_CHAR, '~', lineCount, fileName);
                continue;
            }

            // Get metadata item name
            strName = getToken(&linePtr, " ", &status);
            if(strName==NULL || status) {
                (*nFail)++;
                status = 0;
                psError(PS_ERR_IO, true,
                        PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "name", lineCount, fileName);
                continue;
            }

            flags = (overwrite) ? PS_META_REPLACE : PS_META_DEFAULT;

            // Get the metadata item type
            strType = getToken(&linePtr, " ", &status);
            if(strType==NULL) {
                (*nFail)++;
                status = 0;
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "type",lineCount,
                        fileName);
                continue;
            } else {
                char* tempStrType = strType;
                if(*strType == '*') {
                    flags = PS_META_DUPLICATE_OK;
                    tempStrType = strType+1;
                }

                if(!strncmp(tempStrType, "STR", 3)) {
                    mdType = PS_META_STR;
                } else if(!strncmp(tempStrType, "BOOL", 4)) {
                    mdType = PS_META_BOOL;
                } else if(!strncmp(tempStrType, "S32", 3)) {
                    mdType = PS_META_S32;
                } else if(!strncmp(tempStrType, "F32", 3)) {
                    mdType = PS_META_F32;
                } else if(!strncmp(tempStrType, "F64", 3)) {
                    mdType = PS_META_F64;
                } else {
                    (*nFail)++;
                    psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID, strType, lineCount,
                            fileName);
                    continue;
                }
            }

            if(*strName == '@') {
                vecType = PS_META_PRIMITIVE_TYPE(mdType);
                mdType = PS_META_VEC;
            }

            // Get the metadata item value if there is one.
            strValue = getToken(&linePtr, "#", &status);
            if(status) {
                (*nFail)++;
                status = 0;
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "comment", lineCount,
                        fileName);
                continue;
            }
            if(strValue==NULL) {
                (*nFail)++;
                status = 0;
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "value", lineCount,
                        fileName);
                continue;
            }

            // Not all lines will have comments, so NULL is ok.
            strComment = getToken(&linePtr,"~", &status);
            if(status) {
                (*nFail)++;
                status = 0;
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "comment", lineCount,
                        fileName);
                continue;
            }

            // Create and add metadata item to metadata and parse values
            switch (mdType) {
            case PS_META_STR:
                addStatus = psMetadataAdd(md, PS_LIST_TAIL, strName,
                                          mdType | flags,
                                          strComment, strValue);
                break;
            case PS_META_VEC:
                tempVec = parseVector(strValue, vecType, &status);
                if(!status) {
                    addStatus = psMetadataAdd(md, PS_LIST_TAIL, strName+1,
                                              mdType | flags,
                                              strComment, tempVec);
                } else {
                    status = 0;
                    (*nFail)++;
                    psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName,
                            strType, lineCount, fileName);
                    continue;
                }
                psFree(tempVec);
                break;
            case PS_META_BOOL:
                tempBool = parseBool(strValue, &status);
                if(!status) {
                    addStatus = psMetadataAdd(md, PS_LIST_TAIL, strName,
                                              mdType | flags,
                                              strComment, tempBool);
                } else {
                    status = 0;
                    (*nFail)++;
                    psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName,
                            strType, lineCount, fileName);
                    continue;
                }
                break;
            case PS_META_S32:
                tempInt = (psS32)parseValue(strValue, &status);
                if(!status) {
                    addStatus = psMetadataAdd(md, PS_LIST_TAIL, strName,
                                              mdType | flags,
                                              strComment, tempInt);
                } else {
                    status = 0;
                    (*nFail)++;
                    psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName,
                            strType, lineCount, fileName);
                    continue;
                }
                break;
            case PS_META_F32:
            case PS_META_F64:
                tempDbl = parseValue(strValue, &status);
                if(!status) {
                    addStatus = psMetadataAdd(md, PS_LIST_TAIL, strName,
                                              mdType | flags,
                                              strComment, tempDbl);
                } else {
                    status = 0;
                    (*nFail)++;
                    psError(PS_ERR_IO, true,
                            PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, strName, strType, lineCount,
                            fileName);
                    continue;
                }
                break;
            default:
                (*nFail)++;
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID, mdType, lineCount,
                        fileName);
                continue;
            } // switch
            if (! addStatus) {
                (*nFail)++;
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_OVERWRITE_ITEM, strName, lineCount,
                        fileName);
            }

        } // if ignoreLine
    } // while loop

    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_PTR_CHECK_NULL_GENERAL(ctxt, return);
    md = (psMetadata*)ctxt->sax->_private;
    PS_PTR_CHECK_NULL_GENERAL(md, return);
    input = (xmlParserInputPtr)ctxt->input;
    PS_PTR_CHECK_NULL_GENERAL(input, return);

    // Copy XML strings to psStrings to avoid libxml2/psLib memory corruption problems
    psTagName = psStringCopy(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_PTR_CHECK_NULL_GENERAL(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(attName);
                psAttValue = psStringCopy(attValue);
                psHashAdd(htAtts, psAttName, psAttValue);
                psFree(psAttName);
                psFree(psAttValue);
            } else {
                PS_PTR_CHECK_NULL_GENERAL(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_PTR_CHECK_NULL_GENERAL(ctxt, return);
    md = (psMetadata*)ctxt->sax->_private;
    PS_PTR_CHECK_NULL_GENERAL(md, return);
    input = (xmlParserInputPtr)ctxt->input;
    PS_PTR_CHECK_NULL_GENERAL(input, return);
    metadataItem = psMetadataLookup(md, "htAtts");
    PS_PTR_CHECK_NULL_GENERAL(metadataItem, return);
    PS_PTR_CHECK_NULL_GENERAL(metadataItem->data.list, return);
    metadataItem = (psMetadataItem*)psListGet(metadataItem->data.list,PS_LIST_TAIL);
    htAtts = (psHash*)metadataItem->data.list;
    PS_PTR_CHECK_NULL_GENERAL(htAtts, return);
    fileName = (char*)input->filename;
    PS_PTR_CHECK_NULL_GENERAL(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 = PS_TYPE_PTR;
    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_PTR_CHECK_NULL_GENERAL(ctxt, return);
    md = (psMetadata*)ctxt->sax->_private;
    PS_PTR_CHECK_NULL_GENERAL(md, return);
    input = (xmlParserInputPtr)ctxt->input;
    PS_PTR_CHECK_NULL_GENERAL(input, return);
    tables = psMetadataLookup(md, "htAtts");
    PS_PTR_CHECK_NULL_GENERAL(tables, return);
    PS_PTR_CHECK_NULL_GENERAL(tables->data.list, return);
    table = (psMetadataItem*)psListGet(tables->data.list,PS_LIST_TAIL);
    htAtts = (psHash*)table->data.list;
    PS_PTR_CHECK_NULL_GENERAL(htAtts, return);
    fileName = (char*)input->filename;
    PS_PTR_CHECK_NULL_GENERAL(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_PTR_CHECK_NULL_GENERAL(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_PTR_CHECK_NULL_GENERAL(ctxt, return);
    md = (psMetadata*)ctxt->sax->_private;
    PS_PTR_CHECK_NULL_GENERAL(md, return);
    input = (xmlParserInputPtr)ctxt->input;
    PS_PTR_CHECK_NULL_GENERAL(input, return);
    tables = psMetadataLookup(md, "htAtts");
    PS_PTR_CHECK_NULL_GENERAL(tables, return);
    PS_PTR_CHECK_NULL_GENERAL(tables->data.list, return);
    table = (psMetadataItem*)psListGet(tables->data.list,PS_LIST_TAIL);
    htAtts = (psHash*)table->data.list;
    PS_PTR_CHECK_NULL_GENERAL(htAtts, return);

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

    // Compare start and end tag names
    psStartTagName = psHashLookup(htAtts, "tagName");
    PS_PTR_CHECK_NULL_GENERAL(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*  psMetadataParseConfigXml(psMetadata* md, psU32 *nFail, const char *fileName, psBool overwrite)
{
    xmlSAXHandler saxHandler;


    // Error checks
    PS_PTR_CHECK_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;
}
