/** * @file dao.c * @ingroup dao * * Database abstraction module. * * @author Geoff Davis <geoff@circlemudsquared.org> * * @par Copyright: * Copyright (C) 2006 Geoff Davis <geoff@circlemudsquared.org><br> * Greg Buxton <greg@circlemudsquared.org> * * @par * Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University<br> * CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * * @par * All rights reserved. See license.doc for complete information. * * @package cs * @version 1.0 */ #define __DAO_C__ #include "structs.h" #include "utils.h" #include "base.h" #include "constants.h" #include "dao.h" #include "interpreter.h" #include "log.h" #include "memory.h" #include "sstring.h" /** * Returns whether a DAO key is valid. * @ingroup dao * @param key the string to be tested for validity * @return TRUE if the string is a valid DAO key; * FALSE otherwise */ bool dao_checkKey(const char *key) { if (key && *key != '\0') { /* Declare an iterator variable. */ register const char *p = key; /* Iterate over the string. */ while (*p != '\0') { /* Break if we've found an invalid character. */ if (!isalnum(*p) && *p != '_') { break; } /* Advance the iterator. */ p++; } if (*p == '\0') { return (TRUE); } } return (FALSE); } /** * Frees a DAO and its children. * @ingroup dao * @param dao the DAO to be freed * @return none */ void dao_free(daoData_t *dao) { if (dao == NULL) { log("dao_free(): invalid 'dao' daoData_t."); } else { /* Loop while there are more children. */ while (dao->children) { /* Free each DAO. */ dao_free(dao->children); } if (dao->key) { /* Free the DAO's key. */ sstr_free(dao->key); } if (dao->value) { /* Free the DAO's value. */ sstr_free(dao->value); } /* Unlink the DAO from the head of the children list. */ if (dao->parent && dao->parent->children == dao) { dao->parent->children = dao->next; /* Unlink the DAO from the middle or end of the children list. */ } else if (dao->parent) { /* Declare an iterator variable. */ register daoData_t *temp = dao->parent->children; /* Search for the DAO in the parent DAO's children list. */ while (temp && temp->next != dao) { /* Advance the iterator. */ temp = temp->next; } if (temp && temp->next == dao) { /* Unlink the DAO from the children list. */ temp->next = dao->next; } } /* Free the DAO structure. */ free(dao); } } /** * Removes a DAO from its parent. The specified DAO is removed but is not * freed. The @{dao_free()} function must subsequently be used to free the DAO. * @ingroup dao * @param dao the DAO to be removed * @return none * @see dao_free() */ void dao_fromParent(daoData_t *dao) { if (dao == NULL) { log("dao_fromParent(): invalid 'dao' daoData_t."); } else { /* Unlink the DAO from the head of the children list. */ if (dao->parent && dao->parent->children == dao) { dao->parent->children = dao->next; /* Unlink the DAO from the middle or end of the children list. */ } else if (dao->parent) { /* Declare an iterator variable. */ register daoData_t *temp = dao->parent->children; /* Search for the DAO in the parent DAO's children list. */ while (temp && temp->next != dao) { /* Advance the iterator. */ temp = temp->next; } if (temp && temp->next == dao) { /* Unlink the DAO from the children list. */ temp->next = dao->next; } } /* Re-initialize the next child pointer. */ dao->next = NULL; /* Re-initialize the parent pointer. */ dao->parent = NULL; } } daoData_t *dao_getChild(daoData_t *parentDao, const char *key) { if (parentDao == NULL) { log("dao_getChild(): invalid 'parentDao' daoData_t."); } else if (key == NULL || *key == '\0') { log("dao_getChild(): invalid 'key' string."); } else { /* Declare an iterator variable. */ register daoData_t *childDao = parentDao->children; /* Iterate over the list of children. */ while (childDao) { /* Compare the keys to determine if this is the DAO we seek. */ if (childDao->key && strcasecmp(childDao->key, key) == 0) { return (childDao); } /* Advance to the next DAO in the children list. */ childDao = childDao->next; } } return (NULL); } double dao_keyDouble(daoData_t *dao, double defaultValue) { double retval = defaultValue; if (dao == NULL) { log("dao_keyDouble(): invalid 'dao' daoData_t."); } else { if (dao && dao->key && *(dao->key) != '\0') { retval = strtod(dao->key, NULL); } } return (retval); } int dao_keyInt(daoData_t *dao, int defaultValue) { int retval = defaultValue; if (dao == NULL) { log("dao_valueInt(): invalid 'dao' daoData_t."); } else { if (dao && dao->key && *(dao->key) != '\0') { retval = atoi(dao->key); } } return (retval); } const char *dao_keyString(daoData_t *dao, const char *defaultValue) { const char *retval = defaultValue; if (dao == NULL) { log("dao_valueString(): invalid 'dao' daoData_t."); } else { if (dao && dao->key && *(dao->key) != '\0') { retval = dao->key; } } return (retval); } /** * Gets the value of a DAO key as a type. * @param dao the DAO whose key is to be retreived * @param strings the names of the bits to be interpreted * @param defaultValue the value to be returned if the value of the specified * DAO cannot be coerced to a type * @return the value of the specified DAO coerced to a type, or the specified * default value */ ssize_t dao_keyType(daoData_t *dao, const char *strings[], ssize_t defaultValue) { ssize_t retval = defaultValue; if (dao == NULL) { log("dao_keyType(): invalid 'dao' daoData_t."); } else if (strings == NULL) { log("dao_keyType(): invalid 'strings' string array."); } else { /* Declare an iterator variable. */ register ssize_t stringsSize = 0; /* Count the number of strings in the string list. */ while (strcmp(strings[stringsSize], "\n") != 0) { stringsSize++; } if (isInteger(dao->key, TRUE)) { /* Use the numeric value of the scalar. */ retval = atoi(dao->key); } else { /* Search the string list for the value. */ retval = search_block(dao->key, strings, TRUE); } /* Use the default value if the lookup failed. */ if (retval < 0 || retval >= stringsSize) { retval = defaultValue; } } return (retval); } /** * Inserts the second DAO as a child of the first. * @ingroup dao * @param parentDao the DAO to which the second DAO is to be added as a child * @param childDao the DAO to be added as a child * @return none */ void dao_insertChild(daoData_t *parentDao, daoData_t *childDao) { if (parentDao == NULL) { log("dao_insertChild(): invalid 'parentDao' daoData_t."); } else if (childDao == NULL) { log("dao_insertChild(): invalid 'childDao' daoData_t."); } else if (parentDao == childDao) { log("dao_insertChild(): 'parentDao' and 'childDao' are the same."); } else { /* Extract the child DAO from its parent. */ dao_fromParent(childDao); if (parentDao->children == NULL) { /* Link the child DAO into the parent DAO's children list. */ parentDao->children = childDao; } else { /* Declare an iterator variable. */ register daoData_t *temp = parentDao->children; /* Find the list DAO in the children list. */ while (temp && temp->next) { /* Advance to the next DAO in the children list. */ temp = temp->next; } if (temp) { /* Link the child DAO into the parent DAO's children list. */ temp->next = childDao; } } /* Set the child DAO's next pointer to NULL. */ childDao->next = NULL; /* Set the child DAO's parent pointer to the new parent DAO. */ childDao->parent = parentDao; } } /** * Creates a new bitvector DAO. A container DAO is retunred having one child * DAO per bitvector flag, each assigned a value of "Yes" or "No". * @ingroup dao * @param parentDao the DAO to which the new DAO is to be added as a child * @param key the key with which the new DAO is to be associated * @param strings the names of the bits to be interpreted * @param bits the bitvector value * @return a new DAO, or NULL */ daoData_t *dao_newBits(daoData_t *parentDao, const char *key, const char *strings[], bitvector_t bits) { /* Declare a variable to hold the new DAO. */ daoData_t *newDao = NULL; if (parentDao == NULL) { log("dao_newBits(): invalid 'parentDao' daoData_t."); } else if (key == NULL || *key == '\0') { log("dao_newBits(): invalid 'key' string."); } else if (!dao_checkKey(key)) { log("dao_newBits(): invalid 'key' value '%s'.", key); } else if (strings == NULL) { log("dao_newBits(): invalid 'strings' string list."); } else { /* Allocate a new DAO. */ newDao = CIRCLE_ALLOCN(daoData_t, 1); if (newDao == NULL) { log("dao_newBits(): CIRCLE_ALLOCN() failed."); } else { /* Declare an iterator variable. */ register int i = 0; /* Initialize the new DAO. */ newDao->children = NULL; newDao->key = sstr_create(key); newDao->next = NULL; newDao->parent = NULL; newDao->value = NULL; /* Iterate over the string list. */ for (i = 0; strcmp(strings[i], "\n") != 0; i++) { /* Declare a temporary buffer. */ char buf[MAX_STRING_LENGTH]; /* Make a local copy of the name of the string. */ strlcpy(buf, strings[i], sizeof(buf)); /* Force the first character of the string to lowercase. */ *buf = LOWER(*buf); /* Create the corresponding scalar DAO. */ if (!dao_newScalar(newDao, buf, "%s", YESNO(IS_SET(bits, BITN(i))))) { /* Write an error message to the log. */ log("dao_newBits(): Couldn't create '%s' bit scalar.", buf); /* Stop processing bit strings. */ break; } } /* Make sure we were able to add all of the flags. */ if (strcmp(strings[i], "\n") != 0) { /* Free the container DAO. */ dao_free(newDao); /* Reset the DAO pointer to NULL. */ newDao = NULL; } else { /* Insert the new DAO into the parent DAO's children list. */ dao_insertChild(parentDao, newDao); } } } return (newDao); } /** * Creates a new child DAO. * @ingroup dao * @param parentDao the DAO to which the new DAO is to be added as a child * @param keyFormat the printf-style format of the key with which the new DAO * is to be associated * @return a new child DAO, or NULL */ daoData_t *dao_newChild(daoData_t *parentDao, const char *keyFormat, ...) { /* Declare a variable to hold the new DAO. */ daoData_t *newDao = NULL; if (parentDao == NULL) { log("dao_newChild(): invalid 'parentDao' daoData_t."); } else { /* Declare a buffer to contain the key. */ char key[MAX_STRING_LENGTH] = {'\0'}; /* Build the key from the format string. */ if (keyFormat != NULL && *keyFormat != '\0') { va_list args; va_start(args, keyFormat); vsnprintf(key, sizeof(key), keyFormat, args); va_end(args); } /* Verify that the provided key is legal. */ if (!dao_checkKey(key)) { log("dao_newChild(): illegal key '%s'.", key); } else { /* Allocate a new DAO. */ newDao = CIRCLE_ALLOCN(daoData_t, 1); if (newDao == NULL) { log("dao_newChild(): CIRCLE_ALLOCN() failed."); } else { /* Initialize the new DAO. */ newDao->children = NULL; newDao->key = NULL; newDao->next = NULL; newDao->parent = NULL; newDao->value = NULL; /* Assign the new DAO's key. */ if (keyFormat && *keyFormat != '\0' && *key != '\0') { newDao->key = sstr_create("%s", key); } /* Insert the new DAO into the parent DAO's children list. */ dao_insertChild(parentDao, newDao); } } } return (newDao); } /** * Creates a new document DAO. * @ingroup dao * @return a new DAO of type 'document', or NULL */ daoData_t *dao_newDocument(void) { /* Allocate a new DAO. */ daoData_t *newDao = CIRCLE_ALLOCN(daoData_t, 1); if (newDao == NULL) { log("dao_newDocument(): CIRCLE_ALLOCN() failed."); } else { /* Initialize the new DAO. */ newDao->children = NULL; newDao->key = NULL; newDao->next = NULL; newDao->parent = NULL; newDao->value = NULL; } return (newDao); } /** * Creates a new scalar DAO. * @ingroup dao * @param parentDao the DAO to which the new DAO is to be added as a child * @param key the key with which the new DAO is to be associated * @param format the printf-style format specifier string * @return a new DAO of type 'scalar', or NULL */ daoData_t *dao_newScalar(daoData_t *parentDao, const char *key, const char *valueFormat, ...) { /* Declare a variable to hold the new DAO. */ daoData_t *newDao = NULL; if (parentDao == NULL) { log("dao_newScalar(): invalid 'parentDao' daoData_t."); } else if (key == NULL || *key == '\0') { log("dao_newScalar(): invalid 'key' string."); } else if (!dao_checkKey(key)) { log("dao_newScalar(): invalid 'key' value '%s'.", key); } else { /* Declare a buffer to contain the key. */ char value[MAX_STRING_LENGTH] = {'\0'}; /* Build the key from the format string. */ if (valueFormat != NULL && *valueFormat != '\0') { va_list args; va_start(args, valueFormat); vsnprintf(value, sizeof(value), valueFormat, args); va_end(args); } /* Allocate a new DAO. */ newDao = CIRCLE_ALLOCN(daoData_t, 1); if (newDao == NULL) { log("dao_newScalar(): CIRCLE_ALLOCN() failed."); } else { /* Initialize the new DAO. */ newDao->children = NULL; newDao->key = sstr_create("%s", key); newDao->next = NULL; newDao->parent = NULL; newDao->value = NULL; /* Assign the new DAO's key. */ if (valueFormat && *valueFormat != '\0' && *value != '\0') { newDao->value = sstr_create("%s", value); } /* Insert the new DAO into the parent DAO's children list. */ dao_insertChild(parentDao, newDao); } } return (newDao); } /** * Prints a DAO to a stream. * @ingroup dao * @param stream the file stream to which the DAO is to be written * @param indentLevel the level of indentation * @param dao the DAO to be written to the stream * @return TRUE if the DAO was successfully written to the stream; * FALSE otherwise */ bool dao_printDao(FILE *stream, size_t indentLevel, daoData_t *dao) { bool retcode = FALSE; if (stream == NULL) { log("dao_printDao(): invalid 'stream' FILE."); } else if (dao == NULL) { log("dao_printDao(): invalid 'dao' daoData_t."); } else { /* Declare a variable to contain the length of the prefix. */ size_t prefixLength = 0; /* Assume we've succeeded until we fail. */ retcode = TRUE; if (retcode && indentLevel) { /* Assign the initial prefix length. */ prefixLength += indentLevel << 1; /* Write any indentation to the stream. */ if (fprintf(stream, "%*s", prefixLength, "") <= 0) { retcode = FALSE; } } if (retcode && dao->key && *(dao->key) != '\0') { /* Add to the length of the prefix. */ prefixLength += strlen(dao->key) + 1; /* Write the key to the stream. */ if (fprintf(stream, "%s ", dao->key) <= 0) { retcode = FALSE; } } if (retcode) { if (dao->value && *(dao->value) != '\0') { /* Handle integers special-like. */ if (isInteger(dao->value, TRUE)) { /* Write the integer to to the stream. */ fprintf(stream, "%d", atoi(dao->value)); /* Handle other numeric scalars differently. */ } else if (isNumber(dao->value)) { /* Write the number to to the stream. */ fprintf(stream, "%f", atof(dao->value)); } else { /* Declare an iterator variable. */ register const char *p = dao->value; /* Print the leading quote mark. */ if (retcode && fprintf(stream, "\"") <= 0) { retcode = FALSE; } /* Iterate over the contents of the string. */ while (retcode && *p != '\0') { if (*p == '\n') { if (fprintf(stream, "\\n") <= 0) { retcode = FALSE; } if (retcode && *(p + 1) != '\0') { if (fprintf(stream, "\"\n%*s\"", prefixLength, "") <= 0) { retcode = FALSE; } } } else if (*p == '\"') { if (fprintf(stream, "\\\"") <= 0) { retcode = FALSE; } } else if (*p != '\r') { if (fprintf(stream, "%c", *p) <= 0) { retcode = FALSE; } } /* Advance to the next character. */ ++p; } /* Print the trailing quote mark. */ if (retcode && fprintf(stream, "\"") <= 0) { retcode = FALSE; } } /* Print the trailing semicolon and EOL character. */ if (retcode && fprintf(stream, ";\n") <= 0) { retcode = FALSE; } } else if (dao->children) { /* Declare an iterator variable. */ register daoData_t *temp = NULL; /* Print the opening curly brace. */ if (fprintf(stream, "{\n") <= 0) { retcode = FALSE; } /* Iterate over the parent DAO's child DAOs. */ for (temp = dao->children; retcode && temp; temp = temp->next) { /* Print each child DAO to the stream. */ retcode = dao_printDao(stream, indentLevel + 1, temp); } /* Print the closing curly brace. */ if (fprintf(stream, "%*s}\n", indentLevel << 1, "") <= 0) { retcode = FALSE; } } else { if (fprintf(stream, "{}\n") <= 0) { retcode = FALSE; } } } } return (retcode); } /** * Queries a DAO using a path. * @ingroup dao * @param dao the DAO to be queried * @param path the criteria for which to query * @return the DAO located at the specified query path, or NULL */ daoData_t *dao_query(daoData_t *dao, const char *path) { daoData_t *retval = NULL; if (dao == NULL) { log("dao_query(): invalid 'dao' daoData_t."); } else if (path == NULL || *path == '\0') { log("dao_query(): invalid 'path' string."); } else { /* Declare a buffer to contain path components. */ char buf[MAX_STRING_LENGTH] = {'\0'}; /* Declare an iterator variable. */ register const char *p = path; /* Skip any leading whitespace. */ while (*p != '\0' && isspace(*p)) { p++; } if (*path == '/') { /* Find the root of the DAO hierarchy. */ for (retval = dao; retval && retval->parent; retval = retval->parent); /* Advance to the next character in the path. */ p++; } else { /* Begin our search at the specified DAO. */ retval = dao; } /* Loop until we reach the end of the string. */ while (retval && p && *p != '\0') { /* Advance beyond an initial path separator. */ if (strchr(DAO_PATH_SEPARATOR, *p) != NULL) { p++; } /* Read the next token from the path. */ p = onetoken(buf, sizeof(buf), DAO_PATH_SEPARATOR, p); /* Make certain the parse operation succeeded. */ if (p && *buf != '\0') { if (strcmp(buf, ".") == 0) { /* Select the current DAO. */ retval = retval; } else if (strcmp(buf, "..") == 0) { /* Select the parent DAO. */ retval = retval->parent; } else { /* Search the current DAO for a matching child. */ retval = dao_getChild(retval, buf); } } } /* Handle parse failures. */ if (p == NULL) { retval = NULL; } } return (retval); } /** * Queries a DAO using a path. * @ingroup dao * @param dao the DAO to be queried * @param path the criteria for which to query * @param strings the names of the bits to be interpreted * @param defaultValue the value to be returned if the DAO indicated by the * specified path does not exist or has no value * @return the value of the DAO located at the specified query path, or the * specified default value */ unsigned long dao_queryBits(daoData_t *dao, const char *path, const char *strings[], unsigned long defaultValue) { unsigned long retval = defaultValue; if (dao == NULL) { log("dao_queryBits(): invalid 'dao' daoData_t."); } else if (path == NULL || *path == '\0') { log("dao_queryBits(): invalid 'path' string."); } else if (strings == NULL) { log("dao_queryBits(): invalid 'strings' string array."); } else { /* Get the corresponding child DAO. */ daoData_t *childDao = dao_query(dao, path); /* Test whether a corresponding child DAO was found. */ if (childDao) { /* Coerce the value of the child DAO to a bitvector. */ retval = dao_valueBits(childDao, strings, defaultValue); } } return (retval); } /** * Queries a DAO using a path. * @ingroup dao * @param dao the DAO to be queried * @param path the criteria for which to query * @param defaultValue the value to be returned if the DAO indicated by the * specified path does not exist or has no value * @return the value of the DAO located at the specified query path, or the * specified default value */ double dao_queryDouble(daoData_t *dao, const char *path, double defaultValue) { double retval = defaultValue; if (dao == NULL) { log("dao_queryDouble(): invalid 'dao' daoData_t."); } else if (path == NULL || *path == '\0') { log("dao_queryDouble(): invalid 'path' string."); } else { /* Get the corresponding child DAO. */ daoData_t *childDao = dao_query(dao, path); /* Test the child DAO to ensure it has some scalar value. */ if (childDao) { /* Coerce the scalar value to a double. */ retval = dao_valueDouble(childDao, defaultValue); } } return (retval); } /** * Queries a DAO using a path. * @ingroup dao * @param dao the DAO to be queried * @param path the criteria for which to query * @param defaultValue the value to be returned if the DAO indicated by the * specified path does not exist or has no value * @return the value of the DAO located at the specified query path, or the * specified default value */ int dao_queryInt(daoData_t *dao, const char *path, int defaultValue) { int retval = defaultValue; if (dao == NULL) { log("dao_queryInt(): invalid 'dao' daoData_t."); } else if (path == NULL || *path == '\0') { log("dao_queryInt(): invalid 'path' string."); } else { /* Get the corresponding child DAO. */ daoData_t *childDao = dao_query(dao, path); /* Test the child DAO to ensure it has some scalar value. */ if (childDao) { /* Coerce the scalar value to an int. */ retval = dao_valueInt(childDao, defaultValue); } } return (retval); } /** * Queries a DAO using a path. * @ingroup dao * @param dao the DAO to be queried * @param path the criteria for which to query * @param defaultValue the value to be returned if the DAO indicated by the * specified path does not exist or has no value * @return the value of the DAO located at the specified query path, or the * specified default value */ long dao_queryLong(daoData_t *dao, const char *path, long defaultValue) { long retval = defaultValue; if (dao == NULL) { log("dao_queryInt(): invalid 'dao' daoData_t."); } else if (path == NULL || *path == '\0') { log("dao_queryInt(): invalid 'path' string."); } else { /* Get the corresponding child DAO. */ daoData_t *childDao = dao_query(dao, path); /* Test the child DAO to ensure it has some scalar value. */ if (childDao) { /* Coerce the scalar value to an int. */ retval = dao_valueLong(childDao, defaultValue); } } return (retval); } /** * Queries a DAO using a path. * @param dao the DAO to be queried * @param path the criteria for which to query * @param defaultValue the value to be returned if the DAO indicated by the * specified path does not exist or has no value * @return the value of the DAO located at the specified query path, or the * specified default value */ const char *dao_queryString(daoData_t *dao, const char *path, const char *defaultValue) { const char *retval = defaultValue; if (dao == NULL) { log("dao_queryString(): invalid 'dao' daoData_t."); } else if (path == NULL || *path == '\0') { log("dao_queryString(): invalid 'path' string."); } else { /* Get the corresponding child DAO. */ daoData_t *childDao = dao_query(dao, path); /* Test the child DAO to ensure it has some scalar value. */ if (childDao) { /* Get the string value. */ retval = dao_valueString(childDao, defaultValue); } } return (retval); } /** * Queries a DAO using a path. * @ingroup dao * @param dao the DAO to be queried * @param path the criteria for which to query * @param strings the names of the types to be interpreted * @param defaultValue the value to be returned if the DAO indicated by the * specified path does not exist or has no value * @return the value of the DAO located at the specified query path, or the * specified default value */ ssize_t dao_queryType(daoData_t *dao, const char *path, const char *strings[], ssize_t defaultValue) { unsigned long retval = defaultValue; if (dao == NULL) { log("dao_queryType(): invalid 'dao' daoData_t."); } else if (path == NULL || *path == '\0') { log("dao_queryType(): invalid 'path' string."); } else if (strings == NULL) { log("dao_queryType(): invalid 'strings' string array."); } else { /* Get the corresponding child DAO. */ daoData_t *childDao = dao_query(dao, path); /* Test whether a corresponding child DAO was found. */ if (childDao) { /* Coerce the value of the child DAO to a type code. */ retval = dao_valueType(childDao, strings, defaultValue); } } return (retval); } /** * Skips leading whitespace. * @ingroup dao * @param ch the pointer to the current character being read * @param stream the read from which to read characters * @return none */ #define dao_skipSpaces(ch, stream) \ do { \ while (*(ch) != EOF && isspace(*(ch))) { \ *(ch) = fgetc(stream); \ } \ } while (FALSE) /** * Gets whether a character is a valid identifier character. * @ingroup dao * @param ch the character to be tested * @return TRUE if the character is a valid identifier character; * FALSE otherwise */ #define isidchar(ch) \ (isalnum(ch) || (ch) == '_') /** * Reads a DAO from the specifed stream and adds it to the specified parent * DAO as a child. * @ingroup dao * @param parentDao the DAO to which the new DAO is to be added as a child * @param stream the file stream from which to read the new DAO * @return TRUE if a DAO was successfully read from the stream; * FALSE otherwise */ bool dao_readDao(daoData_t *parentDao, FILE *stream) { bool retcode = FALSE; if (parentDao == NULL) { log("dao_readDao(): invalid 'parentDao' daoData_t."); } else if (stream == NULL) { log("dao_readDao(): invalid 'stream' FILE."); } else { /* Declare a buffer for accumulating scalar values. */ char buf[MAX_STRING_LENGTH]; int ch = EOF; /* Declare the position in the buffer. */ register size_t bufpos = 0; /* Assume we've succeeded until we fail. */ retcode = TRUE; /* Read the first character from the stream. */ ch = fgetc(stream); /* Skip any leading whitespace. */ dao_skipSpaces(&ch, stream); if (ch != EOF) { /* Read a DAO key from the stream. */ while (ch != EOF && isidchar(ch)) { /* Accumulate the character. */ if (bufpos < sizeof(buf) - 1) { buf[bufpos++] = ch; } /* Read the next character in the stream. */ ch = fgetc(stream); } /* Terminate the buffer with a NUL character. */ buf[bufpos] = '\0'; if (ch == EOF) { log("dao_readDao(): read unexpected EOF."); retcode = FALSE; } else { /* Skip any leading whitespace. */ dao_skipSpaces(&ch, stream); if (ch == EOF) { log("dao_readDao(): read unexpected EOF."); retcode = FALSE; } else { /* Create the child DAO. */ daoData_t *childDao = dao_newChild(parentDao, "%s", buf); if (childDao == NULL) { log("dao_readDao(): Could not create child DAO."); } else { /* * We've read the opening brace of a container DAO. Process any * remaining characters up until a closing brace, recursively, as * the contents of the container. */ if (retcode && ch == '{') { /* Loop until the container is finished. */ while (retcode && ch != EOF) { /* Read the next character from the stream. */ ch = fgetc(stream); /* Skip any leading whitespace. */ dao_skipSpaces(&ch, stream); if (ch != EOF && ch != '}') { /* Return the last read character to the stream. */ ungetc(ch, stream); /* Read a child DAO from the stream. */ retcode = dao_readDao(childDao, stream); } else { break; } } if (ch != '}') { log("dao_readDao(): missing } character."); retcode = FALSE; } /* * We've read the opening hash mark of a comment DAO. In the * current implementation, comments are silently ignored. In the * future, comments will be processed and stored within the * structure of the DAO. */ } else if (retcode && ch == '#') { /* Read the next character from the stream. */ ch = fgetc(stream); /* Reset the buffer position. */ bufpos = 0; /* Read until the end of the line. */ while (ch != EOF && ch != '\n') { /* Accumulate the text of the comment. */ if (bufpos < sizeof(buf) - 1) { buf[bufpos++] = ch; } /* Read the next character from the stream. */ ch = fgetc(stream); } /* Add the comment DAO to the parent DAO. */ /* dao_newChild(parentDao, "", "%s", buf); */ /* * We've read the opening quote of a string literal. */ } else if (retcode && ch == '\"') { /* Re-initialize the buffer position. */ bufpos = 0; /* Return the quote character to the string. */ ungetc(ch, stream); /* Read until the end of the string literal. */ while (retcode && ch != EOF) { /* Read the next character from the stream. */ ch = fgetc(stream); /* Skip any spaces. */ dao_skipSpaces(&ch, stream); if (ch == '\"') { /* Read a quoted string from the stream. */ retcode = dao_readString(buf + bufpos, sizeof(buf) - bufpos, stream); if (retcode) { /* Adjust the position in the buffer. */ bufpos = strlen(buf); } } else { /* Stop processing the string. */ break; } } /* Terminate the buffer with a NUL character. */ buf[bufpos] = '\0'; /* Make certain we've terminated the scalar with a semicolon. */ if (retcode && ch != ';') { log("dao_readDao(): missing ; character."); retcode = FALSE; } if (retcode) { /* If the child DAO has a scalar value, free it. */ if (childDao->value) { sstr_free(childDao->value); } /* Set the child DAO's scalar value. */ childDao->value = sstr_create("%s", buf); } /* * We've read the initial character of a number. All numbers are * read as double-precision (64-bit) floating point values. */ } else if (retcode && (ch == '+' || ch == '-' || isdigit(ch))) { /* Declare a flag to indicate we've read a decimal point. */ bool dot = FALSE; /* Re-initialize the buffer position. */ bufpos = 0; /* Handle a leading sign. */ if (ch == '+' || ch == '-') { if (ch == '-') { /* Accumulate the character. */ if (bufpos < sizeof(buf) - 1) { buf[bufpos++] = ch; } } /* Read the next character from the stream. */ ch = fgetc(stream); } /* Read the initial digit sequence. */ while (ch != EOF && (isdigit(ch) || (!dot && ch == '.'))) { /* Record whether we've seen a dot. */ if (ch == '.') { dot = TRUE; } /* Accumulate the character. */ if (bufpos < sizeof(buf) - 1) { buf[bufpos++] = ch; } /* Read the next character from the stream. */ ch = fgetc(stream); } /* The digit sequence cannot end with a decimal. */ if (bufpos > 0 && buf[bufpos - 1] == '.') { retcode = FALSE; } /* Process the exponent. */ if (retcode && (ch == 'e' || ch == 'E')) { /* Accumulate the character. */ if (bufpos < sizeof(buf) - 1) { buf[bufpos++] = ch; } /* Read the next character from the stream. */ ch = fgetc(stream); if (retcode && (ch == '+' || ch == '-')) { /* Accumulate the character. */ if (bufpos < sizeof(buf) - 1) { buf[bufpos++] = ch; } /* Read the next character from the stream. */ ch = fgetc(stream); } else { retcode = FALSE; } /* Collect the remaining digit sequence. */ while (retcode && ch != EOF && isdigit(ch)) { /* Accumulate the character. */ if (bufpos < sizeof(buf) - 1) { buf[bufpos++] = ch; } /* Read the next character from the stream. */ ch = fgetc(stream); } } if (ch != ';') { log("dao_readDao(): missing ; character."); retcode = FALSE; } if (retcode) { /* If the child DAO has a scalar value, free it. */ if (childDao->value) { sstr_free(childDao->value); } /* Set the child DAO's scalar value. */ childDao->value = sstr_create("%s", buf); } /* * All other characters signal an error. */ } else if (retcode) { log("dao_readDao(): read illegal character '%c'.", ch); retcode = FALSE; } } if (retcode && childDao == NULL) { retcode = FALSE; } } } } } return (retcode); } /** * Reads a quoted string from the specified stream. * @ingroup dao * @param buf the buffer into which the string it to be read * @param buflen the length of the buffer * @param stream the file stream from which to read the quoted string * @return TRUE if a string was successfully read from the stream; * FALSE otherwise */ bool dao_readString(char *buf, size_t buflen, FILE *stream) { bool retcode = FALSE; if (buf == NULL) { log("dao_readString(): invalid 'buf' string."); } else if (buflen == 0) { log("dao_readString(): invalid 'buflen' value 0."); } else if (stream == NULL) { log("dao_readString(): invalid 'stream' FILE."); } else { /* Declare the position in the buffer. */ register size_t bufpos = 0; /* Declare a flag to record whether we're in an escape sequence. */ bool escape = FALSE; /* Read the next character from the stream. */ int ch = fgetc(stream); /* Read until the end of the string. */ while (ch != EOF && ch != '\n') { /* Stop reading when we read an unescaped quote. */ if (!escape && ch == '\"') { break; } /* Handle a backslash (escape) sequence. */ if (!escape && ch == '\\') { escape = TRUE; } else if (escape) { /* Handle the \n escape sequence. */ if (ch == 'n' && bufpos < buflen - 2) { buf[bufpos++] = '\r'; buf[bufpos++] = '\n'; /* Handle the \\ escape sequence. */ } else if (ch == '\\' && bufpos < buflen - 1) { buf[bufpos++] = ch; /* Handle the \" escape sequence. */ } else if (ch == '\"' && bufpos < buflen - 1) { buf[bufpos++] = ch; /* Ignore all other escape sequences. */ } else if (bufpos < buflen - 2) { buf[bufpos++] = '\\'; buf[bufpos++] = ch; } /* Reset the escape flag. */ escape = FALSE; } else { /* Accumulate the character. */ if (bufpos < buflen - 1 && ch != '\r') { buf[bufpos++] = ch; } } /* Read the next character from the stream. */ ch = fgetc(stream); } /* Terminate the buffer with a NUL character. */ buf[bufpos] = '\0'; /* We've succeeded if we ended on a quote. */ retcode = (!escape && ch == '\"'); } return (retcode); } /** * Reads a DAO from a file. * @ingroup dao * @param filename the filename of the file to be read * @return the new DAO, or NULL */ daoData_t *dao_readFile(const char *filename) { /* Declare a variable to hold the read DAO. */ daoData_t *daoRoot = NULL; if (filename == NULL || *filename == '\0') { log("dao_readFile(): invalid 'filename' string"); } else { /* Open the input file for reading. */ FILE *stream = fopen(filename, "rt"); if (stream == NULL) { log("Couldn't open file '%s' for reading.", filename); } else { /* Create the DAO document root. */ daoRoot = dao_newDocument(); if (daoRoot == NULL) { log("dao_readFile(): dao_newDocument() failed."); } else { /* Read until we reach the EOF. */ while (!feof(stream) && daoRoot != NULL) { /* Read a DAO from the stream. */ if (!dao_readDao(daoRoot, stream)) { /* Free the root DAO if we have failed. */ dao_free(daoRoot); daoRoot = NULL; } } } /* Close the open stream. */ fclose(stream); } } return (daoRoot); } /** * Deletes the specified key and its corresponding DAO from the specified * parent DAO. * @ingroup dao * @param parentDao the DAO from which the specified key is to be removed * @param key the key to be removed * @return TRUE if the key was successfully deleted; * FALSE otherwise */ bool dao_removeKey(daoData_t *parentDao, const char *key) { return (FALSE); } /** * Saves a DAO to a file. * @ingroup dao * @param dao the DAO to be saved * @param filename the filename of the file to be writted * @return TRUE if the DAO was successfully saved; * FALSE otherwise */ bool dao_saveFile(daoData_t *dao, const char *filename) { bool retcode = FALSE; if (dao == NULL) { log("dao_saveFile(): invalid 'dao' daoData_t."); } else if (filename == NULL || *filename == '\0') { log("dao_saveFile(): invalid 'filename' string."); } else { /* Open the output file for writing. */ FILE *stream = fopen(filename, "wt"); if (stream == NULL) { log("Couldn't open file '%s' for writing.", filename); } else { /* Declare an iterator variable. */ register daoData_t *temp = NULL; /* Assume we've succeeded until we actually fail. */ retcode = TRUE; /* Iterate over the DAO's children. */ for (temp = dao->children; retcode && temp; temp = temp->next) { /* Print each child DAO to the stream. */ retcode = dao_printDao(stream, 0, temp); } /* Close the open stream. */ fclose(stream); if (!retcode) { log("dao_saveFile(): error while writing file '%s'.", filename); } } } return (retcode); } /** * Gets the value of a DAO as a bitvector. * @ingroup dao * @param dao the DAO whose value is to be retreived * @param strings the names of the bits to be interpreted * @param defaultValue the value to be returned if the value of the specified * DAO cannot be coerced to a bitvector * @return the value of the specified DAO coerced to a bitvector, or the * specified default value */ unsigned long dao_valueBits(daoData_t *dao, const char *strings[], unsigned long defaultValue) { unsigned long retval = defaultValue; if (dao == NULL) { log("dao_valueBits(): invalid 'dao' daoData_t."); } else if (strings == NULL) { log("dao_valueBits(): invalid 'strings' string array."); } else { /* Declare an iterator variable. */ register ssize_t stringsSize = 0; /* Count the number of strings in the string list. */ while (strcmp(strings[stringsSize], "\n") != 0) { stringsSize++; } if (isInteger(dao->value, TRUE)) { /* Use the numeric value of the scalar. */ retval = atoi(dao->value); } else if (dao->children) { /* Declare an iterator variable. */ register daoData_t *temp = NULL; /* Set the value of the bitvector to 0. */ retval = 0UL; /* Iterate over the children of the DAO. */ for (temp = dao->children; temp; temp = temp->next) { if (temp->key && *(temp->key) != '\0' && (strcasecmp(temp->value, "YES") == 0 || strcasecmp(temp->value, "ON") == 0 )) { /* Search the string list for the value. */ ssize_t bitno = search_block(temp->key, strings, TRUE); /* Skip the bit if there's no corresponding string. */ if (bitno == -1) { break; } /* Set the corresponding bit of the bitvector. */ SET_BIT(retval, (1 << bitno)); } } } } return (retval); } /** * Gets the value of a DAO as a double. * @ingroup dao * @param dao the DAO whose value is to be retreived * @param defaultValue the value to be returned if the value of the specified * DAO cannot be coerced to a double * @return the value of the specified DAO coerced to a double, or the * specified default value */ double dao_valueDouble(daoData_t *dao, double defaultValue) { double retval = defaultValue; if (dao == NULL) { log("dao_valueDouble(): invalid 'dao' daoData_t."); } else if (dao->value && *(dao->value) != '\0') { if (isNumber(dao->value)) { retval = atof(dao->value); } } return (retval); } /** * Gets the value of a DAO as an integer. * @ingroup dao * @param dao the DAO whose value is to be retreived * @param defaultValue the value to be returned if the value of the specified * DAO cannot be coerced to an integer * @return the value of the specified DAO coerced to an integer, or the * specified default value */ int dao_valueInt(daoData_t *dao, int defaultValue) { int retval = defaultValue; if (dao == NULL) { log("dao_valueInt(): invalid 'dao' daoData_t."); } else if (dao->value && *(dao->value) != '\0') { if (isNumber(dao->value)) { retval = atoi(dao->value); } } return (retval); } /** * Gets the value of a DAO as a long. * @param dao the DAO whose value is to be retreived * @param defaultValue the value to be returned if the value of the specified * DAO cannot be coerced to an integer * @return the value of the specified DAO coerced to an integer, or the * specified default value */ long dao_valueLong(daoData_t *dao, long defaultValue) { long retval = defaultValue; if (dao == NULL) { log("dao_valueLong(): invalid 'dao' daoData_t."); } else if (dao->value && *(dao->value) != '\0') { if (isNumber(dao->value)) { retval = atol(dao->value); } } return (retval); } /** * Gets the value of a DAO as a string. * @ingroup dao * @param dao the DAO whose value is to be retreived * @param defaultValue the value to be returned if the value of the specified * DAO cannot be coerced to a string * @return the value of the specified DAO coerced to a string, or the * specified default value */ const char *dao_valueString(daoData_t *dao, const char *defaultValue) { const char *retval = defaultValue; if (dao == NULL) { log("dao_valueString(): invalid 'dao' daoData_t."); } else if (dao->value && *(dao->value) != '\0') { retval = dao->value; } return (retval); } /** * Gets the value of a DAO as a type. * @ingroup dao * @param dao the DAO whose value is to be retreived * @param strings the names of the bits to be interpreted * @param defaultValue the value to be returned if the value of the specified * DAO cannot be coerced to a type * @return the value of the specified DAO coerced to a type, or the specified * default value */ ssize_t dao_valueType(daoData_t *dao, const char *strings[], ssize_t defaultValue) { ssize_t retval = defaultValue; if (dao == NULL) { log("dao_valueType(): invalid 'dao' daoData_t."); } else if (strings == NULL) { log("dao_valueType(): invalid 'strings' string array."); } else { /* Declare an iterator variable. */ register ssize_t stringsSize = 0; /* Count the number of strings in the string list. */ while (strcmp(strings[stringsSize], "\n") != 0) { stringsSize++; } if (isInteger(dao->value, TRUE)) { /* Use the numeric value of the scalar. */ retval = atoi(dao->value); } else { /* Search the string list for the value. */ retval = search_block(dao->value, strings, TRUE); } /* Use the default value if the lookup failed. */ if (retval < 0 || retval >= stringsSize) { retval = defaultValue; } } return (retval); }