circlemud_squared_0.5.153/cnf/
circlemud_squared_0.5.153/etc/
circlemud_squared_0.5.153/etc/etc/
circlemud_squared_0.5.153/etc/house/
circlemud_squared_0.5.153/etc/misc/
circlemud_squared_0.5.153/etc/plralias/A-E/
circlemud_squared_0.5.153/etc/plralias/F-J/
circlemud_squared_0.5.153/etc/plralias/K-O/
circlemud_squared_0.5.153/etc/plralias/P-T/
circlemud_squared_0.5.153/etc/plralias/U-Z/
circlemud_squared_0.5.153/etc/plralias/ZZZ/
circlemud_squared_0.5.153/etc/plrobjs/
circlemud_squared_0.5.153/etc/plrobjs/A-E/
circlemud_squared_0.5.153/etc/plrobjs/F-J/
circlemud_squared_0.5.153/etc/plrobjs/K-O/
circlemud_squared_0.5.153/etc/plrobjs/P-T/
circlemud_squared_0.5.153/etc/plrobjs/U-Z/
circlemud_squared_0.5.153/etc/plrobjs/ZZZ/
circlemud_squared_0.5.153/etc/text/
circlemud_squared_0.5.153/etc/text/help/
circlemud_squared_0.5.153/src/util/
circlemud_squared_0.5.153/src/util/worldconv/
/**
 * @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);
}