// file: vtable.cpp
// author: Andrew Hynek
// contents: implementation of the VTable class
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "file.h"
#include "vtable.h"
#include "utils.h" // for str_cmp(), log()
VTable::field::~field()
{
if (multiline) {
delete [] multiline;
multiline = NULL;
}
}
VTable::section::~section()
{
*name = '\0';
parent = NULL;
if (sub_tab) {
delete [] sub_tab;
sub_tab = NULL;
}
sub_cnt = 0;
sub_size = 0;
if (field_tab) {
delete [] field_tab;
field_tab = NULL;
}
field_cnt = 0;
field_size = 0;
}
int VTable::NumSections() const
{
return (count_subsections(&top)+1);
}
int VTable::NumSubsections(const char *sect)
{
section *ptr = find_section(sect);
if (!ptr)
return -1;
return ptr->sub_cnt;
}
int VTable::NumFields(const char *sect)
{
section *ptr = find_section(sect);
if (!ptr)
return -1;
return ptr->field_cnt;
}
bool VTable::DoesSectionExist(const char *sect)
{
return (find_section(sect) != NULL);
}
bool VTable::DoesFieldExist(const char *where)
{
return (find_field(where) != NULL);
}
#define SKIP { in->GetLine(line, MAX_LINE_LENGTH*2, FALSE); continue; }
int VTable::Parse(File *in)
{
char line[MAX_LINE_LENGTH*2];
int depth = 0, total_fields = 0;
section *sec_ptr = ⊤
in->GetLine(line, MAX_LINE_LENGTH*2, FALSE);
while (!in->EoF() && str_cmp(line, "BREAK")) {
char *line_ptr = line;
int d;
bool multiline_input = false;
// skip spaces
while (*line_ptr && *line_ptr == ' ')
line_ptr++;
// calculate depth (1 per leading tab)
d = 0;
while (*line_ptr == '\t') {
line_ptr++;
d++;
}
// printf("depth=%d, d=%d, line_ptr=%s\n", depth, d, line_ptr);
if (d > depth) {
log("Warning: depth doesn't match field depth (%d != %d) (%s, line %d)",
d, depth, in->Filename(), in->LineNumber());
SKIP;
} else
while (d < depth) {
// while the current depth < sec_ptr_depth, pull out a level
sec_ptr = sec_ptr->parent;
depth--;
}
{ // check for section title
char *open = strchr(line_ptr, '[');
char *close = strchr(line_ptr, ']');
// if it's there at at the beginning of the line,
// descend into another level
if (open && close && close > open && open == line_ptr) {
// check to see if we need a new table
if (!sec_ptr->sub_tab || sec_ptr->sub_cnt >= sec_ptr->sub_size)
resize_subsection_tab(sec_ptr);
section *new_sec = sec_ptr->sub_tab+sec_ptr->sub_cnt;
sec_ptr->sub_cnt++;
// set the data:
new_sec->parent = sec_ptr;
char *src = open+1, *dst = new_sec->name;
while (*src != ']') { // we know ']' is there, for sure
*(dst++) = *(src++);
}
*dst = '\0';
// set depth and sec_ptr, then skip
sec_ptr = new_sec;
depth++;
//printf("read section title %s, inc'ing depth\n", sec_ptr->name);
SKIP;
}
}
// so we know by this point that we have a potential field,
// so first check to see if we need a new table
if (!sec_ptr->field_tab || sec_ptr->field_cnt >= sec_ptr->field_size)
resize_field_tab(sec_ptr);
// then get the next field entry
field *field_ptr = sec_ptr->field_tab+sec_ptr->field_cnt;
char *name_ptr = field_ptr->name;
// copy the field name, until ":\t" or ":$"
while (*line_ptr) {
if (*line_ptr == ':' &&
(*(line_ptr+1) == '\t' || *(line_ptr+1) == '$'))
break;
if((name_ptr-field_ptr->name) > MAX_FIELD_LENGTH)
field_ptr->name[MAX_FIELD_LENGTH - 1] = '\0';
*(name_ptr++) = *(line_ptr++);
}
*name_ptr = '\0';
// make sure we've got a field name
if (!*line_ptr) {
log("Invalid field: '%s' (%s, line %d)",
line, in->Filename(), in->LineNumber());
SKIP;
}
//printf("continuing with field %s\n", field_ptr->name);
// and if we do, advance the field_cnt and continue
sec_ptr->field_cnt++;
total_fields++;
// advance line_ptr past the ":\t" or ":$", and skip the spaces
if (*line_ptr == ':') {
if (*(line_ptr+1) == '$') {
multiline_input = true;
line_ptr += 2;
} else if (*(line_ptr+1) == '\t')
line_ptr += 2;
while (*line_ptr && (*line_ptr == ' ' || *line_ptr == '\t'))
line_ptr++;
}
// now if we have a single line, just copy the rest of the line
if (!multiline_input) {
memset(field_ptr->line, 0, MAX_LINE_LENGTH);
strncpy(field_ptr->line, line_ptr, MAX_LINE_LENGTH);
} else {
// otherwise use ReadString to get a '~'-terminated multiline string
field_ptr->multiline = in->ReadString();
}
in->GetLine(line, MAX_LINE_LENGTH*2, FALSE);
}
return total_fields;
}
#undef SKIP
int VTable::GetInt(const char *where, int defawlt)
{
field *ptr = find_field(where);
if (!ptr)
return defawlt;
return atoi(ptr->line);
}
long VTable::GetLong(const char *where, long defawlt)
{
field *ptr = find_field(where);
if (!ptr)
return defawlt;
return atol(ptr->line);
}
int VTable::LookupInt(const char *where,
const char **lookup_tab, int defawlt)
{
field *ptr = find_field(where);
if (!ptr)
return defawlt;
int i;
for (i = 0; *lookup_tab[i] != '\n'; i++)
if (!str_cmp(ptr->line, lookup_tab[i]))
return i;
return defawlt;
}
const char *VTable::GetString(const char *where, const char *defawlt)
{
field *ptr = find_field(where);
if (!ptr) {
return defawlt;
}
return (ptr->multiline? ptr->multiline : ptr->line);
}
float VTable::GetFloat(const char *where, float defawlt)
{
field *ptr = find_field(where);
if (!ptr)
return defawlt;
return (float)atof(ptr->line);
}
const char *VTable::GetIndexField(const char *sect_name, int n)
{
section *ptr = find_section(sect_name);
if (!ptr)
return NULL;
return ptr->field_tab[n].name;
}
const char *VTable::GetIndexSection(const char *sect_name, int n)
{
section *ptr = find_section(sect_name);
if (!ptr)
return NULL;
return ptr->sub_tab[n].name;
}
int VTable::GetIndexInt(const char *sect_name, int n, int defawlt)
{
section *ptr = find_section(sect_name);
if (!ptr)
return defawlt;
return atoi(ptr->field_tab[n].line);
}
const char *VTable::GetIndexString(const char *sect_name, int n,
const char *defawlt)
{
section *ptr = find_section(sect_name);
if (!ptr)
return defawlt;
return (ptr->field_tab[n].multiline? ptr->field_tab[n].multiline
: ptr->field_tab[n].line);
}
void VTable::separate(const char *where,
char sect_name[MAX_SECTION_LENGTH],
char field_name[MAX_FIELD_LENGTH])
{
const char *src_ptr = where;
char *sec_ptr = sect_name, *field_ptr = field_name;
*sec_ptr = *field_ptr = '\0';
while (*src_ptr && *src_ptr == ' ')
src_ptr++;
char *delim = strchr(src_ptr, '/');
if (!delim) {
strncpy(field_name, src_ptr, MAX_FIELD_LENGTH);
*(field_name + MAX_FIELD_LENGTH - 1) = '\0';
} else {
if ((delim-src_ptr) < MAX_SECTION_LENGTH) {
strncpy(sect_name, src_ptr, delim-src_ptr);
*(sect_name + (delim-src_ptr)) = '\0';
} else {
strncpy(sect_name, src_ptr, MAX_SECTION_LENGTH);
*(sect_name + MAX_SECTION_LENGTH - 1) = '\0';
}
strncpy(field_name, delim+1, MAX_FIELD_LENGTH);
*(field_name + MAX_FIELD_LENGTH - 1) = '\0';
}
}
void VTable::resize_subsection_tab(section *ptr, int empty)
{
if (ptr->sub_tab && ptr->sub_cnt < ptr->sub_size)
return;
ptr->sub_size += empty;
section *new_tab = new section[ptr->sub_size];
if (ptr->sub_tab) {
for (int k = 0; k < ptr->sub_cnt; k++)
new_tab[k] = ptr->sub_tab[k];
delete [] ptr->sub_tab;
}
ptr->sub_tab = new_tab;
}
void VTable::resize_field_tab(section *ptr, int empty)
{
if (ptr->field_tab && ptr->field_cnt < ptr->field_size)
return;
ptr->field_size += empty;
field *new_tab = new field[ptr->field_size];
if (ptr->field_tab) {
for (int k = 0; k < ptr->field_cnt; k++)
new_tab[k] = ptr->field_tab[k];
delete [] ptr->field_tab;
}
ptr->field_tab = new_tab;
}
VTable::field *VTable::find_field(const char *where)
{
char sect[MAX_SECTION_LENGTH], field_name[MAX_FIELD_LENGTH];
separate(where, sect, field_name);
section *ptr = find_section(sect);
if (!ptr || !*field_name)
return NULL;
for (int i = 0; i < ptr->field_cnt; i++)
if (!str_cmp(ptr->field_tab[i].name, field_name))
return (ptr->field_tab+i);
return NULL;
}
VTable::section *VTable::find_section(const char *sect)
{
if (!sect || !*sect)
return ⊤
return (look_for_section(&top, sect));
}
VTable::section *VTable::look_for_section(section *ptr,
const char *name)
{
if (!ptr)
return NULL;
if (!str_cmp(ptr->name, name))
return ptr;
for (int i = 0; i < ptr->sub_cnt; i++) {
section *ret = look_for_section(ptr->sub_tab+i, name);
if (ret)
return ret;
}
return NULL;
}
int VTable::count_subsections(const section *ptr) const
{
if (!ptr)
return 0;
int count = 0;
for (int i = 0; i < ptr->sub_cnt; i++)
count += count_subsections(ptr->sub_tab+i);
return count;
}