Index: /trunk/psLib/src/astronomy/psMetadataIO.c
===================================================================
--- /trunk/psLib/src/astronomy/psMetadataIO.c	(revision 1973)
+++ /trunk/psLib/src/astronomy/psMetadataIO.c	(revision 1973)
@@ -0,0 +1,638 @@
+/** @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.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2004-10-06 01:17:39 $
+*
+*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#include <fitsio.h>
+#include <string.h>
+#include <ctype.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"
+
+
+/******************************************************************************/
+/*  DEFINE STATEMENTS                                                         */
+/******************************************************************************/
+
+/** Check for FITS errors */
+#define FITS_ERROR(STRING,PS_ERROR)                                                                          \
+fits_get_errstatus(status, fitsErr);                                                                         \
+psError(__func__, STRING, PS_ERROR, fitsErr);                                                                \
+status = 0;                                                                                                  \
+fits_close_file(fd, &status);                                                                                \
+if(status){                                                                                                  \
+    fits_get_errstatus(status, fitsErr);                                                                     \
+    psError(__func__, "Couldn't close FITS file. FITS error: %s", fitsErr);                                  \
+}                                                                                                            \
+status = 0;                                                                                                  \
+return output;
+
+/** 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;                                                                                   \
+}                                                                                                        \
+if(tempVec) {                                                                                            \
+    psFree(tempVec);                                                                                     \
+    tempVec = 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                                          */
+/*****************************************************************************/
+
+
+/** Determines if a line is blank (whitespace only) or a comment, if so returns true. The input string must
+ * be null terminated. */
+bool ignoreLine(char *inString)
+{
+    while(*inString!='\0' && *inString!='#') {
+        if(!isspace(*inString)) {
+            return false;
+        }
+        inString++;
+    }
+
+    return true;
+}
+
+
+/** Removes leading and trailing space from a line, as well as the leading comment # character. The cleaned
+ * line is a new null terminated copy of the original input string. The input string must be null terminated.
+ */
+char *cleanLine(char *inString)
+{
+    char *ptrB = NULL;
+    char *ptrE = NULL;
+    char *cleaned = NULL;
+    int sLen = 0;
+
+
+    sLen = strlen(inString);
+    ptrB = inString;
+
+    /* Skip over # and leading whitespace */
+    while (isspace(*ptrB) || *ptrB=='#') {
+        ptrB++;
+    }
+
+    /* Skip over trailing whitespace */
+    ptrE = inString + sLen;
+    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 repeated occurances of a single character within a line. The input string must be null terminated. */
+int repeatedChars(char *inString, char ch)
+{
+    int count = 0;
+
+
+    while(*inString!='\0') {
+        if(*inString == ch) {
+            count++;
+        }
+        inString++;
+    }
+
+    return count;
+}
+
+/** Returns individual tokens within a line of text based on delimiter. Tokens are new null terminated copies
+ *  of the original input string. */
+char* getToken(char **inString, char *delimiter, bool clean)
+{
+    char *token = NULL;
+    char *cleanToken = NULL;
+    int sLen = 0;
+
+
+    // Skip over leading blanks
+    while(isspace(**inString)) {
+        (*inString)++;
+    }
+
+    // Length of next token
+    sLen = strcspn(*inString, delimiter);
+    if(sLen) {
+
+        // Create null terminated token
+        token = psStringNCopy(*inString, sLen);
+        if(clean) {
+
+            // Remove excess from token
+            cleanToken = cleanLine(token);
+            psFree(token);
+            token = cleanToken;
+        }
+
+        // Skip to end of token
+        (*inString) += sLen;
+    }
+
+    return token;
+}
+
+/** Returns single parsed value as a double precision number. The input string must be cleaned and null
+ * terminated. */
+double parseValue(char *inString, int *status)
+{
+    char *end = NULL;
+    double value = 0.0;
+
+
+    value = strtod(inString, &end);
+    if(*end != '\0') {
+        *status = 1;
+    }
+
+    return value;
+}
+
+/** Returns true or false. 'T', 't', '1', 'F', 'f', and '0' are parsable variations. */
+bool parseBool(char *inString, int *status)
+{
+    bool 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, int *status)
+{
+    char *end = NULL;
+    char *saveValue = NULL;
+    int i = 0;
+    int 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);
+            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(__func__, "Invalid type: %d", elemType);
+            }
+
+            while(*end==' ' || *end==',') {
+                end++;
+            }
+            inString=end;
+        }
+    }
+
+    return vec;
+}
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+void psMetadataItemPrint(FILE * fd, const char *format, const psMetadataItem* restrict metadataItem)
+{
+    psMetadataType type;
+
+    if (fd == NULL) {
+        psError(__func__, "Null file descriptor not allowed");
+        return;
+    }
+
+    if (format == NULL) {
+        psError(__func__, "Null format not allowed");
+        return;
+    }
+
+    if (metadataItem == NULL) {
+        psError(__func__, "Null metadata not allowed");
+        return;
+    }
+
+    type = metadataItem->type;
+
+    switch(type) {
+    case PS_META_BOOL:
+        fprintf(fd, format, metadataItem->data.B);
+        break;
+    case PS_META_S32:
+        fprintf(fd,format, metadataItem->data.S32);
+        break;
+    case PS_META_F32:
+        fprintf(fd, format, metadataItem->data.F32);
+        break;
+    case PS_META_F64:
+        fprintf(fd, format, metadataItem->data.F64);
+        break;
+    case PS_META_STR:
+        fprintf(fd, format, metadataItem->data.V);
+        break;
+    default:
+        psError(__func__, " Invalid psMetadataType to print: %d", type);
+    }
+}
+
+
+psMetadata* psMetadataReadHeader(psMetadata* output, char *extName, int extNum, char *fileName)
+{
+    bool tempBool;
+    bool success;
+    char keyType;
+    char keyName[FITS_LINE_SIZE];
+    char keyValue[FITS_LINE_SIZE];
+    char keyComment[FITS_LINE_SIZE];
+    char fitsErr[MAX_STRING_LENGTH];
+    int i;
+    int hduType = 0;
+    int status = 0;
+    int numKeys = 0;
+    int keyNum = 0;
+    psMetadataType metadataItemType;
+    fitsfile *fd = NULL;
+
+    if(fileName == NULL) {
+        psError(__func__, "Null fileName not allowed");
+        return 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 == 0) {
+        psError(__func__, "Null extName and extNum = 0 not allowed");
+        return NULL;
+    } else if (extName && extNum) {
+        psError(__func__, "Both extName and extNum arguments should not have non zero values.");
+        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':
+            metadataItemType = PS_META_S32;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, atoi(keyValue));
+            break;
+        case 'F':
+            metadataItemType = PS_META_F64;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, atof(keyValue));
+            break;
+        case 'C':
+            metadataItemType = PS_META_STR;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, keyValue);
+            break;
+        case 'L':
+            metadataItemType = PS_META_BOOL;
+            tempBool = (keyValue[0] == 'T') ? 1 : 0;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, tempBool);
+            break;
+        case 'U':
+        case 'X':
+        default:
+            psError(__func__, "Invalid psMetadataType: %c", keyType);
+            return output;
+        }
+
+        if (!success) {
+            psError(__func__, "Failed to add metadata item. Name: %s", keyName);
+            return output;
+        }
+    }
+
+    return output;
+}
+
+
+int psMetadataParseConfig(psMetadata** md, char *fileName, bool overwrite)
+{
+    bool tempBool;
+    char *line = NULL;
+    char *strName = NULL;
+    char *strType = NULL;
+    char *strValue = NULL;
+    char *strComment = NULL;
+    char *linePtr = NULL;
+    int status = 0;
+    int lineCount = 0;
+    int failedLines = 0;
+    psF64 tempValue = 0.0;
+    psMetadataItem *metadataItem = NULL;
+    psVector *tempVec = NULL;
+    FILE *fp = NULL;
+    psElemType elemType;
+    psMetadataType mdType;
+
+
+    // Check for nulls
+    if(fileName == NULL) {
+        psError(__func__, "Null fileName not allowed");
+        return -1;
+    } else if((fp=fopen(fileName, "r")) == NULL) {
+        psError(__func__, "Couldn't open file. Name: %s", fileName);
+        return -1;
+    }
+
+    // Allocate metadata if necessary
+    if (*md == NULL) {
+        *md = psMetadataAlloc();
+    }
+
+    // Create reusable line for continuous read
+    line = (char*)psAlloc(MAX_STRING_LENGTH*sizeof(char));
+    if (line == NULL) {
+        psAbort(__func__, "Failed to allocate reusable line");
+    }
+
+    // While loop to parse the file
+    while(fgets(line, MAX_STRING_LENGTH, fp) != NULL) {
+        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) {
+                failedLines++;
+                psError(__func__, "More than one @ charecter not allowed. Line %d: %s", lineCount, line);
+                continue;
+            } else if(repeatedChars(linePtr, '*') > 1) {
+                failedLines++;
+                psError(__func__, "More than one @ charecter not allowed. Line %d: %s", lineCount, line);
+                continue;
+            }
+
+            // Get metadata item name
+            strName = getToken(&linePtr, " ", true);
+            if(strName==NULL) {
+                failedLines++;
+                psError(__func__, "Null name not allowed. Line %d: %s", lineCount, line);
+                continue;
+            }
+
+            // Get the metadata item type
+            strType = getToken(&linePtr, " ", true);
+            if(strType==NULL) {
+                failedLines++;
+                psError(__func__, "Null type not allowed. Line %d: %s", lineCount, line);
+                continue;
+            } else if(!strncmp(strType, "*", 1)) {
+                mdType = PS_META_ITEM_SET;
+            } else if(!strncmp(strType, "STR", 3)) {
+                mdType = PS_META_STR;
+                elemType = PS_TYPE_PTR;
+            } else if(!strncmp(strType, "BOOL", 3)) {
+                mdType = PS_META_BOOL;
+                elemType = PS_TYPE_U8;
+            } else if(!strncmp(strType, "S32", 3)) {
+                mdType = PS_META_S32;
+                elemType = PS_TYPE_S32;
+            } else if(!strncmp(strType, "F32", 3)) {
+                mdType = PS_META_F32;
+                elemType = PS_TYPE_F32;
+            } else if(!strncmp(strType, "F64", 3)) {
+                mdType = PS_META_F64;
+                elemType = PS_TYPE_F64;
+            } else {
+                failedLines++;
+                psError(__func__, "Invalid psMetadataType. Type: %s Line %d: %s", strType, lineCount, line);
+                continue;
+            }
+
+            if(*strName == '@') {
+                mdType = PS_META_VEC;
+            }
+
+            // Get the metadata item value if there is one. Lines with * don't have values.
+            if(mdType != PS_META_ITEM_SET) {
+                strValue = getToken(&linePtr, "#", true);
+                if(strValue==NULL) {
+                    failedLines++;
+                    psError(__func__, "Null value not allowed. Line %d: %s", lineCount, line);
+                    continue;
+                }
+            }
+
+            // Get the metadata item value if there is one. Not all lines will have comments, so NULL is ok.
+            strComment = getToken(&linePtr,"!!!!", true);
+
+            /* If metadata item is found, 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, strName);
+            if(metadataItem != NULL) {
+                if(metadataItem->type!=PS_META_ITEM_SET) {
+                    if(overwrite) {
+                        psMetadataRemove(*md, PS_LIST_UNKNOWN, strName);
+                    } else {
+                        failedLines++;
+                        psError(__func__, "Overwrite for name not allowed. Name: %s. Line %d: %s", strName,
+                                lineCount, line);
+                        continue;
+                    }
+                }
+            }
+
+            // Create and add metadata item to metadata and parse values
+            switch (mdType) {
+            case PS_META_ITEM_SET:
+                psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, NULL, NULL);
+                break;
+            case PS_META_BOOL:
+                tempBool = parseBool(strValue, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, tempBool);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            case PS_META_S32:
+            case PS_META_F32:
+            case PS_META_F64:
+                tempValue = parseValue(strValue, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, tempValue);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            case PS_META_STR:
+                psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, strValue);
+                break;
+            case PS_META_VEC:
+                tempVec = parseVector(strValue, elemType, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName+1, mdType, strComment, tempVec);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            default:
+                failedLines++;
+                psError(__func__, "Invalid psMetadataType. Type: %d Line %d: %s", mdType, lineCount, line);
+                continue;
+            } // switch
+        } // if ignoreLine
+    } // while loop
+
+    psFree(line);
+
+    return failedLines;
+}
Index: /trunk/psLib/src/astronomy/psMetadataIO.h
===================================================================
--- /trunk/psLib/src/astronomy/psMetadataIO.h	(revision 1973)
+++ /trunk/psLib/src/astronomy/psMetadataIO.h	(revision 1973)
@@ -0,0 +1,70 @@
+/** @file  psMetadataIO.h
+*
+*  @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.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2004-10-06 01:17:39 $
+*
+*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+*/
+#ifndef PS_METADATAIO_H
+#define PS_METADATAIO_H
+
+
+/// @addtogroup Metadata
+/// @{
+
+
+/** Print metadata item to file.
+ *
+ *  Metadata items may be printed to an open file descriptor based on a
+ *  provided format. The format is a sprintf format statement with exactly
+ *  one % formatting command. If the metadata item type is a numeric type,
+ *  this formatting command must also be numeric, and the type conversion
+ *  performed to the value to match the format type. If the metadata type is
+ *  a string, the fromatting command must also be for a string. If the
+ *  metadata type is any other data type, printing is not allowed.
+ *
+ * @return psMetadataItem* : Pointer metadata item.
+ */
+void psMetadataItemPrint(
+    FILE * fd,                         ///< Pointer to file to write metadata item.
+    const char *format,                ///< Format to print metadata item.
+    const psMetadataItem* restrict metadataItem ///< Metadata item to print.
+);
+
+/** Read metadata header.
+ *
+ *  Read a metadata header from file. If the file is not found, an error is
+ *  reported.
+ *
+ *  @return psMetadata* : Pointer to resulting metadata.
+ */
+psMetadata* psMetadataReadHeader(
+    psMetadata* output,                ///< Resulting metadata from read.
+    char *extName,                     ///< File name extension string.
+    int extNum,                        ///< File name extension number. Starts at 1.
+    char *fileName                     ///< Name of file to read.
+);
+
+/** Read metadata configuration file.
+ *
+ *  Loads pre-defined settings by parsing a configuration file into a psMetadata structure.
+ *
+ *  @return int : Number of lines that failed to be read.
+ */
+int psMetadataParseConfig(
+    psMetadata** md,                   ///< Resulting metadata from read.
+    char *fileName,                    ///< Name of file to read.
+    bool overwrite                     ///< Allow overwrite of duplicate specifications.
+);
+
+/// @}
+
+#endif
Index: /trunk/psLib/src/collections/psMetadataIO.c
===================================================================
--- /trunk/psLib/src/collections/psMetadataIO.c	(revision 1973)
+++ /trunk/psLib/src/collections/psMetadataIO.c	(revision 1973)
@@ -0,0 +1,638 @@
+/** @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.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2004-10-06 01:17:39 $
+*
+*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#include <fitsio.h>
+#include <string.h>
+#include <ctype.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"
+
+
+/******************************************************************************/
+/*  DEFINE STATEMENTS                                                         */
+/******************************************************************************/
+
+/** Check for FITS errors */
+#define FITS_ERROR(STRING,PS_ERROR)                                                                          \
+fits_get_errstatus(status, fitsErr);                                                                         \
+psError(__func__, STRING, PS_ERROR, fitsErr);                                                                \
+status = 0;                                                                                                  \
+fits_close_file(fd, &status);                                                                                \
+if(status){                                                                                                  \
+    fits_get_errstatus(status, fitsErr);                                                                     \
+    psError(__func__, "Couldn't close FITS file. FITS error: %s", fitsErr);                                  \
+}                                                                                                            \
+status = 0;                                                                                                  \
+return output;
+
+/** 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;                                                                                   \
+}                                                                                                        \
+if(tempVec) {                                                                                            \
+    psFree(tempVec);                                                                                     \
+    tempVec = 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                                          */
+/*****************************************************************************/
+
+
+/** Determines if a line is blank (whitespace only) or a comment, if so returns true. The input string must
+ * be null terminated. */
+bool ignoreLine(char *inString)
+{
+    while(*inString!='\0' && *inString!='#') {
+        if(!isspace(*inString)) {
+            return false;
+        }
+        inString++;
+    }
+
+    return true;
+}
+
+
+/** Removes leading and trailing space from a line, as well as the leading comment # character. The cleaned
+ * line is a new null terminated copy of the original input string. The input string must be null terminated.
+ */
+char *cleanLine(char *inString)
+{
+    char *ptrB = NULL;
+    char *ptrE = NULL;
+    char *cleaned = NULL;
+    int sLen = 0;
+
+
+    sLen = strlen(inString);
+    ptrB = inString;
+
+    /* Skip over # and leading whitespace */
+    while (isspace(*ptrB) || *ptrB=='#') {
+        ptrB++;
+    }
+
+    /* Skip over trailing whitespace */
+    ptrE = inString + sLen;
+    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 repeated occurances of a single character within a line. The input string must be null terminated. */
+int repeatedChars(char *inString, char ch)
+{
+    int count = 0;
+
+
+    while(*inString!='\0') {
+        if(*inString == ch) {
+            count++;
+        }
+        inString++;
+    }
+
+    return count;
+}
+
+/** Returns individual tokens within a line of text based on delimiter. Tokens are new null terminated copies
+ *  of the original input string. */
+char* getToken(char **inString, char *delimiter, bool clean)
+{
+    char *token = NULL;
+    char *cleanToken = NULL;
+    int sLen = 0;
+
+
+    // Skip over leading blanks
+    while(isspace(**inString)) {
+        (*inString)++;
+    }
+
+    // Length of next token
+    sLen = strcspn(*inString, delimiter);
+    if(sLen) {
+
+        // Create null terminated token
+        token = psStringNCopy(*inString, sLen);
+        if(clean) {
+
+            // Remove excess from token
+            cleanToken = cleanLine(token);
+            psFree(token);
+            token = cleanToken;
+        }
+
+        // Skip to end of token
+        (*inString) += sLen;
+    }
+
+    return token;
+}
+
+/** Returns single parsed value as a double precision number. The input string must be cleaned and null
+ * terminated. */
+double parseValue(char *inString, int *status)
+{
+    char *end = NULL;
+    double value = 0.0;
+
+
+    value = strtod(inString, &end);
+    if(*end != '\0') {
+        *status = 1;
+    }
+
+    return value;
+}
+
+/** Returns true or false. 'T', 't', '1', 'F', 'f', and '0' are parsable variations. */
+bool parseBool(char *inString, int *status)
+{
+    bool 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, int *status)
+{
+    char *end = NULL;
+    char *saveValue = NULL;
+    int i = 0;
+    int 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);
+            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(__func__, "Invalid type: %d", elemType);
+            }
+
+            while(*end==' ' || *end==',') {
+                end++;
+            }
+            inString=end;
+        }
+    }
+
+    return vec;
+}
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+void psMetadataItemPrint(FILE * fd, const char *format, const psMetadataItem* restrict metadataItem)
+{
+    psMetadataType type;
+
+    if (fd == NULL) {
+        psError(__func__, "Null file descriptor not allowed");
+        return;
+    }
+
+    if (format == NULL) {
+        psError(__func__, "Null format not allowed");
+        return;
+    }
+
+    if (metadataItem == NULL) {
+        psError(__func__, "Null metadata not allowed");
+        return;
+    }
+
+    type = metadataItem->type;
+
+    switch(type) {
+    case PS_META_BOOL:
+        fprintf(fd, format, metadataItem->data.B);
+        break;
+    case PS_META_S32:
+        fprintf(fd,format, metadataItem->data.S32);
+        break;
+    case PS_META_F32:
+        fprintf(fd, format, metadataItem->data.F32);
+        break;
+    case PS_META_F64:
+        fprintf(fd, format, metadataItem->data.F64);
+        break;
+    case PS_META_STR:
+        fprintf(fd, format, metadataItem->data.V);
+        break;
+    default:
+        psError(__func__, " Invalid psMetadataType to print: %d", type);
+    }
+}
+
+
+psMetadata* psMetadataReadHeader(psMetadata* output, char *extName, int extNum, char *fileName)
+{
+    bool tempBool;
+    bool success;
+    char keyType;
+    char keyName[FITS_LINE_SIZE];
+    char keyValue[FITS_LINE_SIZE];
+    char keyComment[FITS_LINE_SIZE];
+    char fitsErr[MAX_STRING_LENGTH];
+    int i;
+    int hduType = 0;
+    int status = 0;
+    int numKeys = 0;
+    int keyNum = 0;
+    psMetadataType metadataItemType;
+    fitsfile *fd = NULL;
+
+    if(fileName == NULL) {
+        psError(__func__, "Null fileName not allowed");
+        return 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 == 0) {
+        psError(__func__, "Null extName and extNum = 0 not allowed");
+        return NULL;
+    } else if (extName && extNum) {
+        psError(__func__, "Both extName and extNum arguments should not have non zero values.");
+        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':
+            metadataItemType = PS_META_S32;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, atoi(keyValue));
+            break;
+        case 'F':
+            metadataItemType = PS_META_F64;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, atof(keyValue));
+            break;
+        case 'C':
+            metadataItemType = PS_META_STR;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, keyValue);
+            break;
+        case 'L':
+            metadataItemType = PS_META_BOOL;
+            tempBool = (keyValue[0] == 'T') ? 1 : 0;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, tempBool);
+            break;
+        case 'U':
+        case 'X':
+        default:
+            psError(__func__, "Invalid psMetadataType: %c", keyType);
+            return output;
+        }
+
+        if (!success) {
+            psError(__func__, "Failed to add metadata item. Name: %s", keyName);
+            return output;
+        }
+    }
+
+    return output;
+}
+
+
+int psMetadataParseConfig(psMetadata** md, char *fileName, bool overwrite)
+{
+    bool tempBool;
+    char *line = NULL;
+    char *strName = NULL;
+    char *strType = NULL;
+    char *strValue = NULL;
+    char *strComment = NULL;
+    char *linePtr = NULL;
+    int status = 0;
+    int lineCount = 0;
+    int failedLines = 0;
+    psF64 tempValue = 0.0;
+    psMetadataItem *metadataItem = NULL;
+    psVector *tempVec = NULL;
+    FILE *fp = NULL;
+    psElemType elemType;
+    psMetadataType mdType;
+
+
+    // Check for nulls
+    if(fileName == NULL) {
+        psError(__func__, "Null fileName not allowed");
+        return -1;
+    } else if((fp=fopen(fileName, "r")) == NULL) {
+        psError(__func__, "Couldn't open file. Name: %s", fileName);
+        return -1;
+    }
+
+    // Allocate metadata if necessary
+    if (*md == NULL) {
+        *md = psMetadataAlloc();
+    }
+
+    // Create reusable line for continuous read
+    line = (char*)psAlloc(MAX_STRING_LENGTH*sizeof(char));
+    if (line == NULL) {
+        psAbort(__func__, "Failed to allocate reusable line");
+    }
+
+    // While loop to parse the file
+    while(fgets(line, MAX_STRING_LENGTH, fp) != NULL) {
+        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) {
+                failedLines++;
+                psError(__func__, "More than one @ charecter not allowed. Line %d: %s", lineCount, line);
+                continue;
+            } else if(repeatedChars(linePtr, '*') > 1) {
+                failedLines++;
+                psError(__func__, "More than one @ charecter not allowed. Line %d: %s", lineCount, line);
+                continue;
+            }
+
+            // Get metadata item name
+            strName = getToken(&linePtr, " ", true);
+            if(strName==NULL) {
+                failedLines++;
+                psError(__func__, "Null name not allowed. Line %d: %s", lineCount, line);
+                continue;
+            }
+
+            // Get the metadata item type
+            strType = getToken(&linePtr, " ", true);
+            if(strType==NULL) {
+                failedLines++;
+                psError(__func__, "Null type not allowed. Line %d: %s", lineCount, line);
+                continue;
+            } else if(!strncmp(strType, "*", 1)) {
+                mdType = PS_META_ITEM_SET;
+            } else if(!strncmp(strType, "STR", 3)) {
+                mdType = PS_META_STR;
+                elemType = PS_TYPE_PTR;
+            } else if(!strncmp(strType, "BOOL", 3)) {
+                mdType = PS_META_BOOL;
+                elemType = PS_TYPE_U8;
+            } else if(!strncmp(strType, "S32", 3)) {
+                mdType = PS_META_S32;
+                elemType = PS_TYPE_S32;
+            } else if(!strncmp(strType, "F32", 3)) {
+                mdType = PS_META_F32;
+                elemType = PS_TYPE_F32;
+            } else if(!strncmp(strType, "F64", 3)) {
+                mdType = PS_META_F64;
+                elemType = PS_TYPE_F64;
+            } else {
+                failedLines++;
+                psError(__func__, "Invalid psMetadataType. Type: %s Line %d: %s", strType, lineCount, line);
+                continue;
+            }
+
+            if(*strName == '@') {
+                mdType = PS_META_VEC;
+            }
+
+            // Get the metadata item value if there is one. Lines with * don't have values.
+            if(mdType != PS_META_ITEM_SET) {
+                strValue = getToken(&linePtr, "#", true);
+                if(strValue==NULL) {
+                    failedLines++;
+                    psError(__func__, "Null value not allowed. Line %d: %s", lineCount, line);
+                    continue;
+                }
+            }
+
+            // Get the metadata item value if there is one. Not all lines will have comments, so NULL is ok.
+            strComment = getToken(&linePtr,"!!!!", true);
+
+            /* If metadata item is found, 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, strName);
+            if(metadataItem != NULL) {
+                if(metadataItem->type!=PS_META_ITEM_SET) {
+                    if(overwrite) {
+                        psMetadataRemove(*md, PS_LIST_UNKNOWN, strName);
+                    } else {
+                        failedLines++;
+                        psError(__func__, "Overwrite for name not allowed. Name: %s. Line %d: %s", strName,
+                                lineCount, line);
+                        continue;
+                    }
+                }
+            }
+
+            // Create and add metadata item to metadata and parse values
+            switch (mdType) {
+            case PS_META_ITEM_SET:
+                psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, NULL, NULL);
+                break;
+            case PS_META_BOOL:
+                tempBool = parseBool(strValue, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, tempBool);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            case PS_META_S32:
+            case PS_META_F32:
+            case PS_META_F64:
+                tempValue = parseValue(strValue, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, tempValue);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            case PS_META_STR:
+                psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, strValue);
+                break;
+            case PS_META_VEC:
+                tempVec = parseVector(strValue, elemType, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName+1, mdType, strComment, tempVec);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            default:
+                failedLines++;
+                psError(__func__, "Invalid psMetadataType. Type: %d Line %d: %s", mdType, lineCount, line);
+                continue;
+            } // switch
+        } // if ignoreLine
+    } // while loop
+
+    psFree(line);
+
+    return failedLines;
+}
Index: /trunk/psLib/src/collections/psMetadataIO.h
===================================================================
--- /trunk/psLib/src/collections/psMetadataIO.h	(revision 1973)
+++ /trunk/psLib/src/collections/psMetadataIO.h	(revision 1973)
@@ -0,0 +1,70 @@
+/** @file  psMetadataIO.h
+*
+*  @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.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2004-10-06 01:17:39 $
+*
+*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+*/
+#ifndef PS_METADATAIO_H
+#define PS_METADATAIO_H
+
+
+/// @addtogroup Metadata
+/// @{
+
+
+/** Print metadata item to file.
+ *
+ *  Metadata items may be printed to an open file descriptor based on a
+ *  provided format. The format is a sprintf format statement with exactly
+ *  one % formatting command. If the metadata item type is a numeric type,
+ *  this formatting command must also be numeric, and the type conversion
+ *  performed to the value to match the format type. If the metadata type is
+ *  a string, the fromatting command must also be for a string. If the
+ *  metadata type is any other data type, printing is not allowed.
+ *
+ * @return psMetadataItem* : Pointer metadata item.
+ */
+void psMetadataItemPrint(
+    FILE * fd,                         ///< Pointer to file to write metadata item.
+    const char *format,                ///< Format to print metadata item.
+    const psMetadataItem* restrict metadataItem ///< Metadata item to print.
+);
+
+/** Read metadata header.
+ *
+ *  Read a metadata header from file. If the file is not found, an error is
+ *  reported.
+ *
+ *  @return psMetadata* : Pointer to resulting metadata.
+ */
+psMetadata* psMetadataReadHeader(
+    psMetadata* output,                ///< Resulting metadata from read.
+    char *extName,                     ///< File name extension string.
+    int extNum,                        ///< File name extension number. Starts at 1.
+    char *fileName                     ///< Name of file to read.
+);
+
+/** Read metadata configuration file.
+ *
+ *  Loads pre-defined settings by parsing a configuration file into a psMetadata structure.
+ *
+ *  @return int : Number of lines that failed to be read.
+ */
+int psMetadataParseConfig(
+    psMetadata** md,                   ///< Resulting metadata from read.
+    char *fileName,                    ///< Name of file to read.
+    bool overwrite                     ///< Allow overwrite of duplicate specifications.
+);
+
+/// @}
+
+#endif
Index: /trunk/psLib/src/types/psMetadataConfig.c
===================================================================
--- /trunk/psLib/src/types/psMetadataConfig.c	(revision 1973)
+++ /trunk/psLib/src/types/psMetadataConfig.c	(revision 1973)
@@ -0,0 +1,638 @@
+/** @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.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2004-10-06 01:17:39 $
+*
+*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#include <fitsio.h>
+#include <string.h>
+#include <ctype.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"
+
+
+/******************************************************************************/
+/*  DEFINE STATEMENTS                                                         */
+/******************************************************************************/
+
+/** Check for FITS errors */
+#define FITS_ERROR(STRING,PS_ERROR)                                                                          \
+fits_get_errstatus(status, fitsErr);                                                                         \
+psError(__func__, STRING, PS_ERROR, fitsErr);                                                                \
+status = 0;                                                                                                  \
+fits_close_file(fd, &status);                                                                                \
+if(status){                                                                                                  \
+    fits_get_errstatus(status, fitsErr);                                                                     \
+    psError(__func__, "Couldn't close FITS file. FITS error: %s", fitsErr);                                  \
+}                                                                                                            \
+status = 0;                                                                                                  \
+return output;
+
+/** 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;                                                                                   \
+}                                                                                                        \
+if(tempVec) {                                                                                            \
+    psFree(tempVec);                                                                                     \
+    tempVec = 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                                          */
+/*****************************************************************************/
+
+
+/** Determines if a line is blank (whitespace only) or a comment, if so returns true. The input string must
+ * be null terminated. */
+bool ignoreLine(char *inString)
+{
+    while(*inString!='\0' && *inString!='#') {
+        if(!isspace(*inString)) {
+            return false;
+        }
+        inString++;
+    }
+
+    return true;
+}
+
+
+/** Removes leading and trailing space from a line, as well as the leading comment # character. The cleaned
+ * line is a new null terminated copy of the original input string. The input string must be null terminated.
+ */
+char *cleanLine(char *inString)
+{
+    char *ptrB = NULL;
+    char *ptrE = NULL;
+    char *cleaned = NULL;
+    int sLen = 0;
+
+
+    sLen = strlen(inString);
+    ptrB = inString;
+
+    /* Skip over # and leading whitespace */
+    while (isspace(*ptrB) || *ptrB=='#') {
+        ptrB++;
+    }
+
+    /* Skip over trailing whitespace */
+    ptrE = inString + sLen;
+    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 repeated occurances of a single character within a line. The input string must be null terminated. */
+int repeatedChars(char *inString, char ch)
+{
+    int count = 0;
+
+
+    while(*inString!='\0') {
+        if(*inString == ch) {
+            count++;
+        }
+        inString++;
+    }
+
+    return count;
+}
+
+/** Returns individual tokens within a line of text based on delimiter. Tokens are new null terminated copies
+ *  of the original input string. */
+char* getToken(char **inString, char *delimiter, bool clean)
+{
+    char *token = NULL;
+    char *cleanToken = NULL;
+    int sLen = 0;
+
+
+    // Skip over leading blanks
+    while(isspace(**inString)) {
+        (*inString)++;
+    }
+
+    // Length of next token
+    sLen = strcspn(*inString, delimiter);
+    if(sLen) {
+
+        // Create null terminated token
+        token = psStringNCopy(*inString, sLen);
+        if(clean) {
+
+            // Remove excess from token
+            cleanToken = cleanLine(token);
+            psFree(token);
+            token = cleanToken;
+        }
+
+        // Skip to end of token
+        (*inString) += sLen;
+    }
+
+    return token;
+}
+
+/** Returns single parsed value as a double precision number. The input string must be cleaned and null
+ * terminated. */
+double parseValue(char *inString, int *status)
+{
+    char *end = NULL;
+    double value = 0.0;
+
+
+    value = strtod(inString, &end);
+    if(*end != '\0') {
+        *status = 1;
+    }
+
+    return value;
+}
+
+/** Returns true or false. 'T', 't', '1', 'F', 'f', and '0' are parsable variations. */
+bool parseBool(char *inString, int *status)
+{
+    bool 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, int *status)
+{
+    char *end = NULL;
+    char *saveValue = NULL;
+    int i = 0;
+    int 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);
+            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(__func__, "Invalid type: %d", elemType);
+            }
+
+            while(*end==' ' || *end==',') {
+                end++;
+            }
+            inString=end;
+        }
+    }
+
+    return vec;
+}
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+void psMetadataItemPrint(FILE * fd, const char *format, const psMetadataItem* restrict metadataItem)
+{
+    psMetadataType type;
+
+    if (fd == NULL) {
+        psError(__func__, "Null file descriptor not allowed");
+        return;
+    }
+
+    if (format == NULL) {
+        psError(__func__, "Null format not allowed");
+        return;
+    }
+
+    if (metadataItem == NULL) {
+        psError(__func__, "Null metadata not allowed");
+        return;
+    }
+
+    type = metadataItem->type;
+
+    switch(type) {
+    case PS_META_BOOL:
+        fprintf(fd, format, metadataItem->data.B);
+        break;
+    case PS_META_S32:
+        fprintf(fd,format, metadataItem->data.S32);
+        break;
+    case PS_META_F32:
+        fprintf(fd, format, metadataItem->data.F32);
+        break;
+    case PS_META_F64:
+        fprintf(fd, format, metadataItem->data.F64);
+        break;
+    case PS_META_STR:
+        fprintf(fd, format, metadataItem->data.V);
+        break;
+    default:
+        psError(__func__, " Invalid psMetadataType to print: %d", type);
+    }
+}
+
+
+psMetadata* psMetadataReadHeader(psMetadata* output, char *extName, int extNum, char *fileName)
+{
+    bool tempBool;
+    bool success;
+    char keyType;
+    char keyName[FITS_LINE_SIZE];
+    char keyValue[FITS_LINE_SIZE];
+    char keyComment[FITS_LINE_SIZE];
+    char fitsErr[MAX_STRING_LENGTH];
+    int i;
+    int hduType = 0;
+    int status = 0;
+    int numKeys = 0;
+    int keyNum = 0;
+    psMetadataType metadataItemType;
+    fitsfile *fd = NULL;
+
+    if(fileName == NULL) {
+        psError(__func__, "Null fileName not allowed");
+        return 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 == 0) {
+        psError(__func__, "Null extName and extNum = 0 not allowed");
+        return NULL;
+    } else if (extName && extNum) {
+        psError(__func__, "Both extName and extNum arguments should not have non zero values.");
+        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':
+            metadataItemType = PS_META_S32;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, atoi(keyValue));
+            break;
+        case 'F':
+            metadataItemType = PS_META_F64;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, atof(keyValue));
+            break;
+        case 'C':
+            metadataItemType = PS_META_STR;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, keyValue);
+            break;
+        case 'L':
+            metadataItemType = PS_META_BOOL;
+            tempBool = (keyValue[0] == 'T') ? 1 : 0;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, tempBool);
+            break;
+        case 'U':
+        case 'X':
+        default:
+            psError(__func__, "Invalid psMetadataType: %c", keyType);
+            return output;
+        }
+
+        if (!success) {
+            psError(__func__, "Failed to add metadata item. Name: %s", keyName);
+            return output;
+        }
+    }
+
+    return output;
+}
+
+
+int psMetadataParseConfig(psMetadata** md, char *fileName, bool overwrite)
+{
+    bool tempBool;
+    char *line = NULL;
+    char *strName = NULL;
+    char *strType = NULL;
+    char *strValue = NULL;
+    char *strComment = NULL;
+    char *linePtr = NULL;
+    int status = 0;
+    int lineCount = 0;
+    int failedLines = 0;
+    psF64 tempValue = 0.0;
+    psMetadataItem *metadataItem = NULL;
+    psVector *tempVec = NULL;
+    FILE *fp = NULL;
+    psElemType elemType;
+    psMetadataType mdType;
+
+
+    // Check for nulls
+    if(fileName == NULL) {
+        psError(__func__, "Null fileName not allowed");
+        return -1;
+    } else if((fp=fopen(fileName, "r")) == NULL) {
+        psError(__func__, "Couldn't open file. Name: %s", fileName);
+        return -1;
+    }
+
+    // Allocate metadata if necessary
+    if (*md == NULL) {
+        *md = psMetadataAlloc();
+    }
+
+    // Create reusable line for continuous read
+    line = (char*)psAlloc(MAX_STRING_LENGTH*sizeof(char));
+    if (line == NULL) {
+        psAbort(__func__, "Failed to allocate reusable line");
+    }
+
+    // While loop to parse the file
+    while(fgets(line, MAX_STRING_LENGTH, fp) != NULL) {
+        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) {
+                failedLines++;
+                psError(__func__, "More than one @ charecter not allowed. Line %d: %s", lineCount, line);
+                continue;
+            } else if(repeatedChars(linePtr, '*') > 1) {
+                failedLines++;
+                psError(__func__, "More than one @ charecter not allowed. Line %d: %s", lineCount, line);
+                continue;
+            }
+
+            // Get metadata item name
+            strName = getToken(&linePtr, " ", true);
+            if(strName==NULL) {
+                failedLines++;
+                psError(__func__, "Null name not allowed. Line %d: %s", lineCount, line);
+                continue;
+            }
+
+            // Get the metadata item type
+            strType = getToken(&linePtr, " ", true);
+            if(strType==NULL) {
+                failedLines++;
+                psError(__func__, "Null type not allowed. Line %d: %s", lineCount, line);
+                continue;
+            } else if(!strncmp(strType, "*", 1)) {
+                mdType = PS_META_ITEM_SET;
+            } else if(!strncmp(strType, "STR", 3)) {
+                mdType = PS_META_STR;
+                elemType = PS_TYPE_PTR;
+            } else if(!strncmp(strType, "BOOL", 3)) {
+                mdType = PS_META_BOOL;
+                elemType = PS_TYPE_U8;
+            } else if(!strncmp(strType, "S32", 3)) {
+                mdType = PS_META_S32;
+                elemType = PS_TYPE_S32;
+            } else if(!strncmp(strType, "F32", 3)) {
+                mdType = PS_META_F32;
+                elemType = PS_TYPE_F32;
+            } else if(!strncmp(strType, "F64", 3)) {
+                mdType = PS_META_F64;
+                elemType = PS_TYPE_F64;
+            } else {
+                failedLines++;
+                psError(__func__, "Invalid psMetadataType. Type: %s Line %d: %s", strType, lineCount, line);
+                continue;
+            }
+
+            if(*strName == '@') {
+                mdType = PS_META_VEC;
+            }
+
+            // Get the metadata item value if there is one. Lines with * don't have values.
+            if(mdType != PS_META_ITEM_SET) {
+                strValue = getToken(&linePtr, "#", true);
+                if(strValue==NULL) {
+                    failedLines++;
+                    psError(__func__, "Null value not allowed. Line %d: %s", lineCount, line);
+                    continue;
+                }
+            }
+
+            // Get the metadata item value if there is one. Not all lines will have comments, so NULL is ok.
+            strComment = getToken(&linePtr,"!!!!", true);
+
+            /* If metadata item is found, 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, strName);
+            if(metadataItem != NULL) {
+                if(metadataItem->type!=PS_META_ITEM_SET) {
+                    if(overwrite) {
+                        psMetadataRemove(*md, PS_LIST_UNKNOWN, strName);
+                    } else {
+                        failedLines++;
+                        psError(__func__, "Overwrite for name not allowed. Name: %s. Line %d: %s", strName,
+                                lineCount, line);
+                        continue;
+                    }
+                }
+            }
+
+            // Create and add metadata item to metadata and parse values
+            switch (mdType) {
+            case PS_META_ITEM_SET:
+                psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, NULL, NULL);
+                break;
+            case PS_META_BOOL:
+                tempBool = parseBool(strValue, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, tempBool);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            case PS_META_S32:
+            case PS_META_F32:
+            case PS_META_F64:
+                tempValue = parseValue(strValue, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, tempValue);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            case PS_META_STR:
+                psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, strValue);
+                break;
+            case PS_META_VEC:
+                tempVec = parseVector(strValue, elemType, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName+1, mdType, strComment, tempVec);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            default:
+                failedLines++;
+                psError(__func__, "Invalid psMetadataType. Type: %d Line %d: %s", mdType, lineCount, line);
+                continue;
+            } // switch
+        } // if ignoreLine
+    } // while loop
+
+    psFree(line);
+
+    return failedLines;
+}
Index: /trunk/psLib/src/types/psMetadataConfig.h
===================================================================
--- /trunk/psLib/src/types/psMetadataConfig.h	(revision 1973)
+++ /trunk/psLib/src/types/psMetadataConfig.h	(revision 1973)
@@ -0,0 +1,70 @@
+/** @file  psMetadataIO.h
+*
+*  @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.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2004-10-06 01:17:39 $
+*
+*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+*/
+#ifndef PS_METADATAIO_H
+#define PS_METADATAIO_H
+
+
+/// @addtogroup Metadata
+/// @{
+
+
+/** Print metadata item to file.
+ *
+ *  Metadata items may be printed to an open file descriptor based on a
+ *  provided format. The format is a sprintf format statement with exactly
+ *  one % formatting command. If the metadata item type is a numeric type,
+ *  this formatting command must also be numeric, and the type conversion
+ *  performed to the value to match the format type. If the metadata type is
+ *  a string, the fromatting command must also be for a string. If the
+ *  metadata type is any other data type, printing is not allowed.
+ *
+ * @return psMetadataItem* : Pointer metadata item.
+ */
+void psMetadataItemPrint(
+    FILE * fd,                         ///< Pointer to file to write metadata item.
+    const char *format,                ///< Format to print metadata item.
+    const psMetadataItem* restrict metadataItem ///< Metadata item to print.
+);
+
+/** Read metadata header.
+ *
+ *  Read a metadata header from file. If the file is not found, an error is
+ *  reported.
+ *
+ *  @return psMetadata* : Pointer to resulting metadata.
+ */
+psMetadata* psMetadataReadHeader(
+    psMetadata* output,                ///< Resulting metadata from read.
+    char *extName,                     ///< File name extension string.
+    int extNum,                        ///< File name extension number. Starts at 1.
+    char *fileName                     ///< Name of file to read.
+);
+
+/** Read metadata configuration file.
+ *
+ *  Loads pre-defined settings by parsing a configuration file into a psMetadata structure.
+ *
+ *  @return int : Number of lines that failed to be read.
+ */
+int psMetadataParseConfig(
+    psMetadata** md,                   ///< Resulting metadata from read.
+    char *fileName,                    ///< Name of file to read.
+    bool overwrite                     ///< Allow overwrite of duplicate specifications.
+);
+
+/// @}
+
+#endif
Index: /trunk/psLib/src/xml/psXML.c
===================================================================
--- /trunk/psLib/src/xml/psXML.c	(revision 1973)
+++ /trunk/psLib/src/xml/psXML.c	(revision 1973)
@@ -0,0 +1,638 @@
+/** @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.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2004-10-06 01:17:39 $
+*
+*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#include <fitsio.h>
+#include <string.h>
+#include <ctype.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"
+
+
+/******************************************************************************/
+/*  DEFINE STATEMENTS                                                         */
+/******************************************************************************/
+
+/** Check for FITS errors */
+#define FITS_ERROR(STRING,PS_ERROR)                                                                          \
+fits_get_errstatus(status, fitsErr);                                                                         \
+psError(__func__, STRING, PS_ERROR, fitsErr);                                                                \
+status = 0;                                                                                                  \
+fits_close_file(fd, &status);                                                                                \
+if(status){                                                                                                  \
+    fits_get_errstatus(status, fitsErr);                                                                     \
+    psError(__func__, "Couldn't close FITS file. FITS error: %s", fitsErr);                                  \
+}                                                                                                            \
+status = 0;                                                                                                  \
+return output;
+
+/** 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;                                                                                   \
+}                                                                                                        \
+if(tempVec) {                                                                                            \
+    psFree(tempVec);                                                                                     \
+    tempVec = 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                                          */
+/*****************************************************************************/
+
+
+/** Determines if a line is blank (whitespace only) or a comment, if so returns true. The input string must
+ * be null terminated. */
+bool ignoreLine(char *inString)
+{
+    while(*inString!='\0' && *inString!='#') {
+        if(!isspace(*inString)) {
+            return false;
+        }
+        inString++;
+    }
+
+    return true;
+}
+
+
+/** Removes leading and trailing space from a line, as well as the leading comment # character. The cleaned
+ * line is a new null terminated copy of the original input string. The input string must be null terminated.
+ */
+char *cleanLine(char *inString)
+{
+    char *ptrB = NULL;
+    char *ptrE = NULL;
+    char *cleaned = NULL;
+    int sLen = 0;
+
+
+    sLen = strlen(inString);
+    ptrB = inString;
+
+    /* Skip over # and leading whitespace */
+    while (isspace(*ptrB) || *ptrB=='#') {
+        ptrB++;
+    }
+
+    /* Skip over trailing whitespace */
+    ptrE = inString + sLen;
+    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 repeated occurances of a single character within a line. The input string must be null terminated. */
+int repeatedChars(char *inString, char ch)
+{
+    int count = 0;
+
+
+    while(*inString!='\0') {
+        if(*inString == ch) {
+            count++;
+        }
+        inString++;
+    }
+
+    return count;
+}
+
+/** Returns individual tokens within a line of text based on delimiter. Tokens are new null terminated copies
+ *  of the original input string. */
+char* getToken(char **inString, char *delimiter, bool clean)
+{
+    char *token = NULL;
+    char *cleanToken = NULL;
+    int sLen = 0;
+
+
+    // Skip over leading blanks
+    while(isspace(**inString)) {
+        (*inString)++;
+    }
+
+    // Length of next token
+    sLen = strcspn(*inString, delimiter);
+    if(sLen) {
+
+        // Create null terminated token
+        token = psStringNCopy(*inString, sLen);
+        if(clean) {
+
+            // Remove excess from token
+            cleanToken = cleanLine(token);
+            psFree(token);
+            token = cleanToken;
+        }
+
+        // Skip to end of token
+        (*inString) += sLen;
+    }
+
+    return token;
+}
+
+/** Returns single parsed value as a double precision number. The input string must be cleaned and null
+ * terminated. */
+double parseValue(char *inString, int *status)
+{
+    char *end = NULL;
+    double value = 0.0;
+
+
+    value = strtod(inString, &end);
+    if(*end != '\0') {
+        *status = 1;
+    }
+
+    return value;
+}
+
+/** Returns true or false. 'T', 't', '1', 'F', 'f', and '0' are parsable variations. */
+bool parseBool(char *inString, int *status)
+{
+    bool 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, int *status)
+{
+    char *end = NULL;
+    char *saveValue = NULL;
+    int i = 0;
+    int 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);
+            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(__func__, "Invalid type: %d", elemType);
+            }
+
+            while(*end==' ' || *end==',') {
+                end++;
+            }
+            inString=end;
+        }
+    }
+
+    return vec;
+}
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+void psMetadataItemPrint(FILE * fd, const char *format, const psMetadataItem* restrict metadataItem)
+{
+    psMetadataType type;
+
+    if (fd == NULL) {
+        psError(__func__, "Null file descriptor not allowed");
+        return;
+    }
+
+    if (format == NULL) {
+        psError(__func__, "Null format not allowed");
+        return;
+    }
+
+    if (metadataItem == NULL) {
+        psError(__func__, "Null metadata not allowed");
+        return;
+    }
+
+    type = metadataItem->type;
+
+    switch(type) {
+    case PS_META_BOOL:
+        fprintf(fd, format, metadataItem->data.B);
+        break;
+    case PS_META_S32:
+        fprintf(fd,format, metadataItem->data.S32);
+        break;
+    case PS_META_F32:
+        fprintf(fd, format, metadataItem->data.F32);
+        break;
+    case PS_META_F64:
+        fprintf(fd, format, metadataItem->data.F64);
+        break;
+    case PS_META_STR:
+        fprintf(fd, format, metadataItem->data.V);
+        break;
+    default:
+        psError(__func__, " Invalid psMetadataType to print: %d", type);
+    }
+}
+
+
+psMetadata* psMetadataReadHeader(psMetadata* output, char *extName, int extNum, char *fileName)
+{
+    bool tempBool;
+    bool success;
+    char keyType;
+    char keyName[FITS_LINE_SIZE];
+    char keyValue[FITS_LINE_SIZE];
+    char keyComment[FITS_LINE_SIZE];
+    char fitsErr[MAX_STRING_LENGTH];
+    int i;
+    int hduType = 0;
+    int status = 0;
+    int numKeys = 0;
+    int keyNum = 0;
+    psMetadataType metadataItemType;
+    fitsfile *fd = NULL;
+
+    if(fileName == NULL) {
+        psError(__func__, "Null fileName not allowed");
+        return 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 == 0) {
+        psError(__func__, "Null extName and extNum = 0 not allowed");
+        return NULL;
+    } else if (extName && extNum) {
+        psError(__func__, "Both extName and extNum arguments should not have non zero values.");
+        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':
+            metadataItemType = PS_META_S32;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, atoi(keyValue));
+            break;
+        case 'F':
+            metadataItemType = PS_META_F64;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, atof(keyValue));
+            break;
+        case 'C':
+            metadataItemType = PS_META_STR;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, keyValue);
+            break;
+        case 'L':
+            metadataItemType = PS_META_BOOL;
+            tempBool = (keyValue[0] == 'T') ? 1 : 0;
+            success = psMetadataAdd(output, PS_LIST_TAIL, keyName, metadataItemType, keyComment, tempBool);
+            break;
+        case 'U':
+        case 'X':
+        default:
+            psError(__func__, "Invalid psMetadataType: %c", keyType);
+            return output;
+        }
+
+        if (!success) {
+            psError(__func__, "Failed to add metadata item. Name: %s", keyName);
+            return output;
+        }
+    }
+
+    return output;
+}
+
+
+int psMetadataParseConfig(psMetadata** md, char *fileName, bool overwrite)
+{
+    bool tempBool;
+    char *line = NULL;
+    char *strName = NULL;
+    char *strType = NULL;
+    char *strValue = NULL;
+    char *strComment = NULL;
+    char *linePtr = NULL;
+    int status = 0;
+    int lineCount = 0;
+    int failedLines = 0;
+    psF64 tempValue = 0.0;
+    psMetadataItem *metadataItem = NULL;
+    psVector *tempVec = NULL;
+    FILE *fp = NULL;
+    psElemType elemType;
+    psMetadataType mdType;
+
+
+    // Check for nulls
+    if(fileName == NULL) {
+        psError(__func__, "Null fileName not allowed");
+        return -1;
+    } else if((fp=fopen(fileName, "r")) == NULL) {
+        psError(__func__, "Couldn't open file. Name: %s", fileName);
+        return -1;
+    }
+
+    // Allocate metadata if necessary
+    if (*md == NULL) {
+        *md = psMetadataAlloc();
+    }
+
+    // Create reusable line for continuous read
+    line = (char*)psAlloc(MAX_STRING_LENGTH*sizeof(char));
+    if (line == NULL) {
+        psAbort(__func__, "Failed to allocate reusable line");
+    }
+
+    // While loop to parse the file
+    while(fgets(line, MAX_STRING_LENGTH, fp) != NULL) {
+        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) {
+                failedLines++;
+                psError(__func__, "More than one @ charecter not allowed. Line %d: %s", lineCount, line);
+                continue;
+            } else if(repeatedChars(linePtr, '*') > 1) {
+                failedLines++;
+                psError(__func__, "More than one @ charecter not allowed. Line %d: %s", lineCount, line);
+                continue;
+            }
+
+            // Get metadata item name
+            strName = getToken(&linePtr, " ", true);
+            if(strName==NULL) {
+                failedLines++;
+                psError(__func__, "Null name not allowed. Line %d: %s", lineCount, line);
+                continue;
+            }
+
+            // Get the metadata item type
+            strType = getToken(&linePtr, " ", true);
+            if(strType==NULL) {
+                failedLines++;
+                psError(__func__, "Null type not allowed. Line %d: %s", lineCount, line);
+                continue;
+            } else if(!strncmp(strType, "*", 1)) {
+                mdType = PS_META_ITEM_SET;
+            } else if(!strncmp(strType, "STR", 3)) {
+                mdType = PS_META_STR;
+                elemType = PS_TYPE_PTR;
+            } else if(!strncmp(strType, "BOOL", 3)) {
+                mdType = PS_META_BOOL;
+                elemType = PS_TYPE_U8;
+            } else if(!strncmp(strType, "S32", 3)) {
+                mdType = PS_META_S32;
+                elemType = PS_TYPE_S32;
+            } else if(!strncmp(strType, "F32", 3)) {
+                mdType = PS_META_F32;
+                elemType = PS_TYPE_F32;
+            } else if(!strncmp(strType, "F64", 3)) {
+                mdType = PS_META_F64;
+                elemType = PS_TYPE_F64;
+            } else {
+                failedLines++;
+                psError(__func__, "Invalid psMetadataType. Type: %s Line %d: %s", strType, lineCount, line);
+                continue;
+            }
+
+            if(*strName == '@') {
+                mdType = PS_META_VEC;
+            }
+
+            // Get the metadata item value if there is one. Lines with * don't have values.
+            if(mdType != PS_META_ITEM_SET) {
+                strValue = getToken(&linePtr, "#", true);
+                if(strValue==NULL) {
+                    failedLines++;
+                    psError(__func__, "Null value not allowed. Line %d: %s", lineCount, line);
+                    continue;
+                }
+            }
+
+            // Get the metadata item value if there is one. Not all lines will have comments, so NULL is ok.
+            strComment = getToken(&linePtr,"!!!!", true);
+
+            /* If metadata item is found, 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, strName);
+            if(metadataItem != NULL) {
+                if(metadataItem->type!=PS_META_ITEM_SET) {
+                    if(overwrite) {
+                        psMetadataRemove(*md, PS_LIST_UNKNOWN, strName);
+                    } else {
+                        failedLines++;
+                        psError(__func__, "Overwrite for name not allowed. Name: %s. Line %d: %s", strName,
+                                lineCount, line);
+                        continue;
+                    }
+                }
+            }
+
+            // Create and add metadata item to metadata and parse values
+            switch (mdType) {
+            case PS_META_ITEM_SET:
+                psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, NULL, NULL);
+                break;
+            case PS_META_BOOL:
+                tempBool = parseBool(strValue, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, tempBool);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            case PS_META_S32:
+            case PS_META_F32:
+            case PS_META_F64:
+                tempValue = parseValue(strValue, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, tempValue);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            case PS_META_STR:
+                psMetadataAdd(*md, PS_LIST_TAIL, strName, mdType, strComment, strValue);
+                break;
+            case PS_META_VEC:
+                tempVec = parseVector(strValue, elemType, &status);
+                if(!status) {
+                    psMetadataAdd(*md, PS_LIST_TAIL, strName+1, mdType, strComment, tempVec);
+                } else {
+                    status = 0;
+                    failedLines++;
+                    psError(__func__, "Failed to parse value. Value: %s Line %d: %s", strValue, lineCount, line);
+                    continue;
+                }
+                break;
+            default:
+                failedLines++;
+                psError(__func__, "Invalid psMetadataType. Type: %d Line %d: %s", mdType, lineCount, line);
+                continue;
+            } // switch
+        } // if ignoreLine
+    } // while loop
+
+    psFree(line);
+
+    return failedLines;
+}
Index: /trunk/psLib/src/xml/psXML.h
===================================================================
--- /trunk/psLib/src/xml/psXML.h	(revision 1973)
+++ /trunk/psLib/src/xml/psXML.h	(revision 1973)
@@ -0,0 +1,70 @@
+/** @file  psMetadataIO.h
+*
+*  @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.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2004-10-06 01:17:39 $
+*
+*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+*/
+#ifndef PS_METADATAIO_H
+#define PS_METADATAIO_H
+
+
+/// @addtogroup Metadata
+/// @{
+
+
+/** Print metadata item to file.
+ *
+ *  Metadata items may be printed to an open file descriptor based on a
+ *  provided format. The format is a sprintf format statement with exactly
+ *  one % formatting command. If the metadata item type is a numeric type,
+ *  this formatting command must also be numeric, and the type conversion
+ *  performed to the value to match the format type. If the metadata type is
+ *  a string, the fromatting command must also be for a string. If the
+ *  metadata type is any other data type, printing is not allowed.
+ *
+ * @return psMetadataItem* : Pointer metadata item.
+ */
+void psMetadataItemPrint(
+    FILE * fd,                         ///< Pointer to file to write metadata item.
+    const char *format,                ///< Format to print metadata item.
+    const psMetadataItem* restrict metadataItem ///< Metadata item to print.
+);
+
+/** Read metadata header.
+ *
+ *  Read a metadata header from file. If the file is not found, an error is
+ *  reported.
+ *
+ *  @return psMetadata* : Pointer to resulting metadata.
+ */
+psMetadata* psMetadataReadHeader(
+    psMetadata* output,                ///< Resulting metadata from read.
+    char *extName,                     ///< File name extension string.
+    int extNum,                        ///< File name extension number. Starts at 1.
+    char *fileName                     ///< Name of file to read.
+);
+
+/** Read metadata configuration file.
+ *
+ *  Loads pre-defined settings by parsing a configuration file into a psMetadata structure.
+ *
+ *  @return int : Number of lines that failed to be read.
+ */
+int psMetadataParseConfig(
+    psMetadata** md,                   ///< Resulting metadata from read.
+    char *fileName,                    ///< Name of file to read.
+    bool overwrite                     ///< Allow overwrite of duplicate specifications.
+);
+
+/// @}
+
+#endif
