/**
* @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 "sysdep.h"
#include "structs.h"
#include "utils.h"
#include "main.h"
#include "dao.h"
#include "interpreter.h"
/*
* Added here for simpilicity's sake
*/
#define CIRCLE_ALLOCN(typeName, elemCount) \
((sizeof(typeName) * (elemCount)) > 0 ? \
((typeName*) calloc((elemCount), sizeof(typeName))) : (typeName*) 0)
/*
* And now to the DAO code
*/
/**
* Returns whether a DAO key is valid.
* @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.
* @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. */
free(dao->key);
}
if (dao->value) {
/* Free the DAO's value. */
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.
* @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.
* @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 child 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 = strdup(key);
}
/* Insert the new DAO into the parent DAO's children list. */
dao_insertChild(parentDao, newDao);
}
}
}
return (newDao);
}
/**
* Creates a new document 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.
* @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 = strdup(key);
newDao->next = NULL;
newDao->parent = NULL;
newDao->value = NULL;
/* Assign the new DAO's key. */
if (valueFormat && *valueFormat != '\0' && *value != '\0') {
newDao->value = strdup(value);
}
/* Insert the new DAO into the parent DAO's children list. */
dao_insertChild(parentDao, newDao);
}
}
return (newDao);
}
/**
* Prints a DAO to a stream.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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.
* @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];
/* 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. */
int 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) {
free(childDao->value);
}
/* Set the child DAO's scalar value. */
childDao->value = strdup(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) {
free(childDao->value);
}
/* Set the child DAO's scalar value. */
childDao->value = strdup(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.
* @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.
* @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.
* @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.
* @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 {
/* Assume we've succeeded until we actually fail. */
retcode = TRUE;
/* Declare an iterator variable. */
register daoData_t *temp = NULL;
/* 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.
* @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.
* @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.
* @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.
* @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.
* @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);
}