mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-13 00:24:12 +00:00
16c6818612
The array has to be allocated using byte elements and thus the size of the array is the number of bytes, but it needs to be the actual number of elements in the array. Problem caused by not knowing the actual type (and C not having type variables anyway).
1229 lines
24 KiB
C
1229 lines
24 KiB
C
/*
|
|
qfplist.c
|
|
|
|
Property list management
|
|
|
|
Copyright (C) 2000 Jeff Teunissen <deek@d2dc.net>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if defined(_WIN32) && defined(HAVE_MALLOC_H)
|
|
#include <malloc.h>
|
|
#endif
|
|
|
|
#include "qfalloca.h"
|
|
|
|
#include "QF/darray.h"
|
|
#include "QF/dstring.h"
|
|
#include "QF/hash.h"
|
|
#include "QF/qfplist.h"
|
|
#include "QF/qtypes.h"
|
|
#include "QF/sys.h"
|
|
#include "QF/va.h"
|
|
|
|
/*
|
|
Generic property list item.
|
|
*/
|
|
struct plitem_s {
|
|
pltype_t type;
|
|
int line;
|
|
void *data;
|
|
};
|
|
|
|
/*
|
|
Dictionaries
|
|
*/
|
|
struct dictkey_s {
|
|
char *key;
|
|
plitem_t *value;
|
|
};
|
|
typedef struct dictkey_s dictkey_t;
|
|
|
|
/*
|
|
Arrays
|
|
*/
|
|
struct plarray_s {
|
|
int numvals; ///< Number of items in array
|
|
int maxvals; ///< Number of items that can be stored
|
|
///< before a realloc is necesary.
|
|
struct plitem_s **values; ///< Array data
|
|
};
|
|
typedef struct plarray_s plarray_t;
|
|
|
|
/*
|
|
Typeless, unformatted binary data
|
|
*/
|
|
struct plbinary_s {
|
|
size_t size;
|
|
void *data;
|
|
};
|
|
typedef struct plbinary_s plbinary_t;
|
|
|
|
typedef struct pldata_s { // Unparsed property list string
|
|
const char *ptr;
|
|
unsigned int end;
|
|
unsigned int pos;
|
|
unsigned int line;
|
|
const char *error;
|
|
} pldata_t;
|
|
|
|
// Ugly defines for fast checking and conversion from char to number
|
|
#define inrange(ch,min,max) ((ch) >= (min) && (ch) <= (max))
|
|
#define char2num(ch) \
|
|
(inrange((ch), '0', '9') ? ((ch) - '0') \
|
|
: 10 + (inrange((ch), 'a', 'f') ? ((ch) - 'a') \
|
|
: ((ch) - 'A')))
|
|
|
|
static const char *pl_types[] = {
|
|
"dictionary",
|
|
"array",
|
|
"biinary",
|
|
"string",
|
|
};
|
|
|
|
static byte quotable_bitmap[32];
|
|
static inline int
|
|
is_quotable (byte x)
|
|
{
|
|
return quotable_bitmap[x / 8] & (1 << (x % 8));
|
|
}
|
|
|
|
static void
|
|
init_quotables (void)
|
|
{
|
|
const char *unquotables = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz!#$%&*+-./:?@|~_^";
|
|
const byte *c;
|
|
memset (quotable_bitmap, ~0, sizeof (quotable_bitmap));
|
|
for (c = (byte *) unquotables; *c; c++)
|
|
quotable_bitmap[*c / 8] &= ~(1 << (*c % 8));
|
|
}
|
|
|
|
static plitem_t *PL_ParsePropertyListItem (pldata_t *);
|
|
static qboolean PL_SkipSpace (pldata_t *);
|
|
static char *PL_ParseQuotedString (pldata_t *);
|
|
static char *PL_ParseUnquotedString (pldata_t *);
|
|
static char *PL_ParseData (pldata_t *, int *);
|
|
|
|
static const char *
|
|
dict_get_key (const void *i, void *unused)
|
|
{
|
|
dictkey_t *item = (dictkey_t *) i;
|
|
return item->key;
|
|
}
|
|
|
|
static void
|
|
dict_free (void *i, void *unused)
|
|
{
|
|
dictkey_t *item = (dictkey_t *) i;
|
|
free (item->key);
|
|
if (item->value) // Make descended stuff get freed
|
|
PL_Free (item->value);
|
|
free (item);
|
|
}
|
|
|
|
static plitem_t *
|
|
PL_NewItem (pltype_t type)
|
|
{
|
|
plitem_t *item = calloc (1, sizeof (plitem_t));
|
|
item->type = type;
|
|
return item;
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_NewDictionary (void)
|
|
{
|
|
plitem_t *item = PL_NewItem (QFDictionary);
|
|
//FIXME need a per-thread hashlink freelist for plist to be thread-safe
|
|
hashtab_t *dict = Hash_NewTable (1021, dict_get_key, dict_free, NULL, 0);
|
|
item->data = dict;
|
|
return item;
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_NewArray (void)
|
|
{
|
|
plitem_t *item = PL_NewItem (QFArray);
|
|
plarray_t *array = calloc (1, sizeof (plarray_t));
|
|
item->data = array;
|
|
return item;
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_NewData (void *data, size_t size)
|
|
{
|
|
plitem_t *item = PL_NewItem (QFBinary);
|
|
plbinary_t *bin = malloc (sizeof (plbinary_t));
|
|
item->data = bin;
|
|
bin->data = data;
|
|
bin->size = size;
|
|
return item;
|
|
}
|
|
|
|
static plitem_t *
|
|
new_string (char *str, int line)
|
|
{
|
|
plitem_t *item = PL_NewItem (QFString);
|
|
item->data = str;
|
|
item->line = line;
|
|
return item;
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_NewString (const char *str)
|
|
{
|
|
return new_string (strdup (str), 0);
|
|
}
|
|
|
|
VISIBLE void
|
|
PL_Free (plitem_t *item)
|
|
{
|
|
switch (item->type) {
|
|
case QFDictionary:
|
|
Hash_DelTable (item->data);
|
|
break;
|
|
|
|
case QFArray: {
|
|
int i = ((plarray_t *) item->data)->numvals;
|
|
|
|
while (i-- > 0) {
|
|
PL_Free (((plarray_t *) item->data)->values[i]);
|
|
}
|
|
free (((plarray_t *) item->data)->values);
|
|
free (item->data);
|
|
}
|
|
break;
|
|
|
|
case QFBinary:
|
|
free (((plbinary_t *) item->data)->data);
|
|
free (item->data);
|
|
break;
|
|
|
|
case QFString:
|
|
free (item->data);
|
|
break;
|
|
}
|
|
free (item);
|
|
}
|
|
|
|
VISIBLE const char *
|
|
PL_String (const plitem_t *string)
|
|
{
|
|
if (string->type != QFString)
|
|
return NULL;
|
|
return string->data;
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_ObjectForKey (plitem_t *dict, const char *key)
|
|
{
|
|
hashtab_t *table = (hashtab_t *) dict->data;
|
|
dictkey_t *k;
|
|
|
|
if (dict->type != QFDictionary)
|
|
return NULL;
|
|
|
|
k = (dictkey_t *) Hash_Find (table, key);
|
|
return k ? k->value : NULL;
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_RemoveObjectForKey (plitem_t *dict, const char *key)
|
|
{
|
|
hashtab_t *table = (hashtab_t *) dict->data;
|
|
dictkey_t *k;
|
|
plitem_t *value;
|
|
|
|
if (dict->type != QFDictionary)
|
|
return NULL;
|
|
|
|
k = (dictkey_t *) Hash_Del (table, key);
|
|
if (!k)
|
|
return NULL;
|
|
value = k->value;
|
|
k->value = 0;
|
|
dict_free (k, 0);
|
|
return value;
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_D_AllKeys (plitem_t *dict)
|
|
{
|
|
void **list, **l;
|
|
dictkey_t *current;
|
|
plitem_t *array;
|
|
|
|
if (dict->type != QFDictionary)
|
|
return NULL;
|
|
|
|
if (!(l = list = Hash_GetList ((hashtab_t *) dict->data)))
|
|
return NULL;
|
|
|
|
if (!(array = PL_NewArray ()))
|
|
return NULL;
|
|
|
|
while ((current = (dictkey_t *) *l++)) {
|
|
PL_A_AddObject (array, PL_NewString (current->key));
|
|
}
|
|
free (list);
|
|
|
|
return array;
|
|
}
|
|
|
|
VISIBLE int
|
|
PL_D_NumKeys (plitem_t *dict)
|
|
{
|
|
if (dict->type != QFDictionary)
|
|
return 0;
|
|
return Hash_NumElements ((hashtab_t *) dict->data);
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_ObjectAtIndex (plitem_t *array, int index)
|
|
{
|
|
plarray_t *arr = (plarray_t *) array->data;
|
|
|
|
if (array->type != QFArray)
|
|
return NULL;
|
|
|
|
return index >= 0 && index < arr->numvals ? arr->values[index] : NULL;
|
|
}
|
|
|
|
VISIBLE qboolean
|
|
PL_D_AddObject (plitem_t *dict, const char *key, plitem_t *value)
|
|
{
|
|
dictkey_t *k;
|
|
|
|
if (dict->type != QFDictionary)
|
|
return false;
|
|
|
|
if ((k = Hash_Find ((hashtab_t *)dict->data, key))) {
|
|
PL_Free ((plitem_t *) k->value);
|
|
k->value = value;
|
|
} else {
|
|
k = malloc (sizeof (dictkey_t));
|
|
|
|
if (!k)
|
|
return false;
|
|
|
|
k->key = strdup (key);
|
|
k->value = value;
|
|
|
|
Hash_Add ((hashtab_t *)dict->data, k);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
VISIBLE qboolean
|
|
PL_A_InsertObjectAtIndex (plitem_t *array, plitem_t *item, int index)
|
|
{
|
|
plarray_t *arr;
|
|
|
|
if (array->type != QFArray)
|
|
return false;
|
|
|
|
arr = (plarray_t *)array->data;
|
|
|
|
if (arr->numvals == arr->maxvals) {
|
|
int size = (arr->maxvals + 128) * sizeof (plitem_t *);
|
|
plitem_t **tmp = realloc (arr->values, size);
|
|
|
|
if (!tmp)
|
|
return false;
|
|
|
|
arr->maxvals += 128;
|
|
arr->values = tmp;
|
|
memset (arr->values + arr->numvals, 0,
|
|
(arr->maxvals - arr->numvals) * sizeof (plitem_t *));
|
|
}
|
|
|
|
if (index == -1)
|
|
index = arr->numvals;
|
|
|
|
if (index < 0 || index > arr->numvals)
|
|
return false;
|
|
|
|
memmove (arr->values + index + 1, arr->values + index,
|
|
(arr->numvals - index) * sizeof (plitem_t *));
|
|
arr->values[index] = item;
|
|
arr->numvals++;
|
|
return true;
|
|
}
|
|
|
|
VISIBLE qboolean
|
|
PL_A_AddObject (plitem_t *array, plitem_t *item)
|
|
{
|
|
return PL_A_InsertObjectAtIndex (array, item, -1);
|
|
}
|
|
|
|
VISIBLE int
|
|
PL_A_NumObjects (plitem_t *array)
|
|
{
|
|
if (array->type != QFArray)
|
|
return 0;
|
|
return ((plarray_t *) array->data)->numvals;
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_RemoveObjectAtIndex (plitem_t *array, int index)
|
|
{
|
|
plarray_t *arr;
|
|
plitem_t *item;
|
|
|
|
if (array->type != QFArray)
|
|
return 0;
|
|
|
|
arr = (plarray_t *)array->data;
|
|
|
|
if (index < 0 || index >= arr->numvals)
|
|
return 0;
|
|
|
|
item = arr->values[index];
|
|
arr->numvals--;
|
|
while (index < arr->numvals) {
|
|
arr->values[index] = arr->values[index + 1];
|
|
index++;
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
static qboolean
|
|
PL_SkipSpace (pldata_t *pl)
|
|
{
|
|
while (pl->pos < pl->end) {
|
|
char c = pl->ptr[pl->pos];
|
|
|
|
if (!isspace ((byte) c)) {
|
|
if (c == '/' && pl->pos < pl->end - 1) { // check for comments
|
|
if (pl->ptr[pl->pos + 1] == '/') {
|
|
pl->pos += 2;
|
|
|
|
while (pl->pos < pl->end) {
|
|
c = pl->ptr[pl->pos];
|
|
|
|
if (c == '\n')
|
|
break;
|
|
pl->pos++;
|
|
}
|
|
if (pl->pos >= pl->end) {
|
|
pl->error = "Reached end of string in comment";
|
|
return false;
|
|
}
|
|
} else if (pl->ptr[pl->pos + 1] == '*') { // "/*" comments
|
|
pl->pos += 2;
|
|
|
|
while (pl->pos < pl->end) {
|
|
c = pl->ptr[pl->pos];
|
|
|
|
if (c == '\n') {
|
|
pl->line++;
|
|
} else if (c == '*' && pl->pos < pl->end - 1
|
|
&& pl->ptr[pl->pos+1] == '/') {
|
|
pl->pos++;
|
|
break;
|
|
}
|
|
pl->pos++;
|
|
}
|
|
if (pl->pos >= pl->end) {
|
|
pl->error = "Reached end of string in comment";
|
|
return false;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
if (c == '\n') {
|
|
pl->line++;
|
|
}
|
|
pl->pos++;
|
|
}
|
|
pl->error = "Reached end of string";
|
|
return false;
|
|
}
|
|
|
|
static inline byte
|
|
from_hex (byte a)
|
|
{
|
|
if (a >= '0' && a <= '9')
|
|
return a - '0';
|
|
if (a >= 'A' && a <= 'F')
|
|
return a - 'A' + 10;
|
|
return a - 'a' + 10;
|
|
}
|
|
|
|
static inline byte
|
|
make_byte (byte h, byte l)
|
|
{
|
|
return (from_hex (h) << 4) | from_hex (l);
|
|
}
|
|
|
|
static char *
|
|
PL_ParseData (pldata_t *pl, int *len)
|
|
{
|
|
unsigned start = ++pl->pos;
|
|
int nibbles = 0, i;
|
|
char *str, c;
|
|
|
|
while (pl->pos < pl->end) {
|
|
c = pl->ptr[pl->pos++];
|
|
if (isxdigit ((byte) c)) {
|
|
nibbles++;
|
|
continue;
|
|
}
|
|
if (c == '>') {
|
|
if (nibbles & 1) {
|
|
pl->error = "invalid data, missing nibble";
|
|
return NULL;
|
|
}
|
|
*len = nibbles / 2;
|
|
str = malloc (*len);
|
|
for (i = 0; i < *len; i++)
|
|
str[i] = make_byte (pl->ptr[start + i * 2],
|
|
pl->ptr[start + i * 2 + 1]);
|
|
return str;
|
|
}
|
|
pl->error = "invalid character in data";
|
|
return NULL;
|
|
}
|
|
pl->error = "Reached end of string while parsing data";
|
|
return NULL;
|
|
}
|
|
|
|
static char *
|
|
PL_ParseQuotedString (pldata_t *pl)
|
|
{
|
|
unsigned int start = ++pl->pos;
|
|
unsigned int escaped = 0;
|
|
unsigned int shrink = 0;
|
|
qboolean hex = false;
|
|
qboolean long_string = false;
|
|
char *str;
|
|
|
|
if (pl->ptr[pl->pos] == '"' &&
|
|
pl->ptr[pl->pos + 1] == '"') {
|
|
long_string = true;
|
|
start += 2;
|
|
pl->pos += 2;
|
|
}
|
|
|
|
while (pl->pos < pl->end) {
|
|
|
|
char c = pl->ptr[pl->pos];
|
|
|
|
if (escaped) {
|
|
if (escaped == 1 && inrange (c, '0', '7')) {
|
|
escaped++;
|
|
hex = false;
|
|
} else if (escaped > 1) {
|
|
if (escaped == 2 && c == 'x') {
|
|
hex = true;
|
|
shrink++;
|
|
escaped++;
|
|
} else if (hex && inrange (escaped, 3, 4)
|
|
&& isxdigit ((byte) c)) {
|
|
shrink++;
|
|
escaped++;
|
|
} else if (inrange (escaped, 1, 3) && inrange (c, '0', '7')) {
|
|
shrink++;
|
|
escaped++;
|
|
} else {
|
|
pl->pos--;
|
|
escaped = 0;
|
|
}
|
|
} else {
|
|
escaped = 0;
|
|
}
|
|
} else {
|
|
if (c == '\\') {
|
|
escaped = 1;
|
|
shrink++;
|
|
} else if (c == '"'
|
|
&& (!long_string || (pl->ptr[pl->pos + 1] == '"'
|
|
&& pl->ptr[pl->pos + 2] == '"'))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (c == '\n') {
|
|
pl->line++;
|
|
}
|
|
|
|
pl->pos++;
|
|
}
|
|
|
|
if (pl->pos >= pl->end) {
|
|
pl->error = "Reached end of string while parsing quoted string";
|
|
return NULL;
|
|
}
|
|
|
|
if (pl->pos - start - shrink == 0) {
|
|
str = strdup ("");
|
|
} else {
|
|
char *chars = alloca(pl->pos - start - shrink);
|
|
unsigned int j;
|
|
unsigned int k;
|
|
|
|
escaped = 0;
|
|
hex = false;
|
|
|
|
for (j = start, k = 0; j < pl->pos; j++) {
|
|
|
|
char c = pl->ptr[j];
|
|
|
|
if (escaped) {
|
|
if (escaped == 1 && inrange (c, '0', '7')) {
|
|
chars[k] = c - '0';
|
|
hex = false;
|
|
escaped++;
|
|
} else if (escaped > 1) {
|
|
if (escaped == 2 && c == 'x') {
|
|
hex = true;
|
|
escaped++;
|
|
} else if (hex && inrange (escaped, 3, 4)
|
|
&& isxdigit ((byte) c)) {
|
|
chars[k] <<= 4;
|
|
chars[k] |= char2num (c);
|
|
escaped++;
|
|
} else if (inrange (escaped, 1, 3)
|
|
&& inrange (c, '0', '7')) {
|
|
chars[k] <<= 3;
|
|
chars[k] |= (c - '0');
|
|
escaped++;
|
|
} else {
|
|
escaped = 0;
|
|
j--;
|
|
k++;
|
|
}
|
|
} else {
|
|
escaped = 0;
|
|
switch (c) {
|
|
case 'a':
|
|
chars[k] = '\a';
|
|
break;
|
|
case 'b':
|
|
chars[k] = '\b';
|
|
break;
|
|
case 't':
|
|
chars[k] = '\t';
|
|
break;
|
|
case 'r':
|
|
chars[k] = '\r';
|
|
break;
|
|
case 'n':
|
|
chars[k] = '\n';
|
|
break;
|
|
case 'v':
|
|
chars[k] = '\v';
|
|
break;
|
|
case 'f':
|
|
chars[k] = '\f';
|
|
break;
|
|
default:
|
|
chars[k] = c;
|
|
break;
|
|
}
|
|
k++;
|
|
}
|
|
} else {
|
|
chars[k] = c;
|
|
if (c == '\\') {
|
|
escaped = 1;
|
|
} else {
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
str = strncat (calloc ((pl->pos - start - shrink) + 1, 1), chars,
|
|
pl->pos - start - shrink);
|
|
}
|
|
if (long_string)
|
|
pl->pos += 2;
|
|
pl->pos++;
|
|
return str;
|
|
}
|
|
|
|
static char *
|
|
PL_ParseUnquotedString (pldata_t *pl)
|
|
{
|
|
unsigned int start = pl->pos;
|
|
char *str;
|
|
|
|
while (pl->pos < pl->end) {
|
|
if (is_quotable (pl->ptr[pl->pos]))
|
|
break;
|
|
pl->pos++;
|
|
}
|
|
str = strncat (calloc ((pl->pos - start) + 1, 1), &pl->ptr[start],
|
|
pl->pos - start);
|
|
return str;
|
|
}
|
|
|
|
static plitem_t *
|
|
PL_ParsePropertyListItem (pldata_t *pl)
|
|
{
|
|
plitem_t *item = NULL;
|
|
|
|
if (!PL_SkipSpace (pl))
|
|
return NULL;
|
|
|
|
switch (pl->ptr[pl->pos]) {
|
|
case '{':
|
|
{
|
|
item = PL_NewDictionary ();
|
|
item->line = pl->line;
|
|
|
|
pl->pos++;
|
|
|
|
while (PL_SkipSpace (pl) && pl->ptr[pl->pos] != '}') {
|
|
plitem_t *key;
|
|
plitem_t *value;
|
|
|
|
if (!(key = PL_ParsePropertyListItem (pl))) {
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(PL_SkipSpace (pl))) {
|
|
PL_Free (key);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
if (key->type != QFString) {
|
|
pl->error = "Key is not a string";
|
|
PL_Free (key);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
if (pl->ptr[pl->pos] != '=') {
|
|
pl->error = "Unexpected character (expected '=')";
|
|
PL_Free (key);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
pl->pos++;
|
|
|
|
// If there is no value, lose the key
|
|
if (!(value = PL_ParsePropertyListItem (pl))) {
|
|
PL_Free (key);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(PL_SkipSpace (pl))) {
|
|
PL_Free (key);
|
|
PL_Free (value);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
if (pl->ptr[pl->pos] == ';') {
|
|
pl->pos++;
|
|
} else if (pl->ptr[pl->pos] != '}') {
|
|
pl->error = "Unexpected character (wanted ';' or '}')";
|
|
PL_Free (key);
|
|
PL_Free (value);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
// Add the key/value pair to the dictionary
|
|
if (!PL_D_AddObject (item, PL_String (key), value)) {
|
|
PL_Free (key);
|
|
PL_Free (value);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
PL_Free (key);
|
|
}
|
|
|
|
if (pl->pos >= pl->end) { // Catch the error
|
|
pl->error = "Unexpected end of string when parsing dictionary";
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
pl->pos++;
|
|
|
|
return item;
|
|
}
|
|
|
|
case '(': {
|
|
item = PL_NewArray ();
|
|
item->line = pl->line;
|
|
|
|
pl->pos++;
|
|
|
|
while (PL_SkipSpace (pl) && pl->ptr[pl->pos] != ')') {
|
|
plitem_t *value;
|
|
|
|
if (!(value = PL_ParsePropertyListItem (pl))) {
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(PL_SkipSpace (pl))) {
|
|
PL_Free (value);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
if (pl->ptr[pl->pos] == ',') {
|
|
pl->pos++;
|
|
} else if (pl->ptr[pl->pos] != ')') {
|
|
pl->error = "Unexpected character (wanted ',' or ')')";
|
|
PL_Free (value);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
|
|
if (!PL_A_AddObject (item, value)) {
|
|
pl->error = "Unexpected character (too many items in array)";
|
|
PL_Free (value);
|
|
PL_Free (item);
|
|
return NULL;
|
|
}
|
|
}
|
|
pl->pos++;
|
|
|
|
return item;
|
|
}
|
|
|
|
case '<': {
|
|
int len;
|
|
char *str = PL_ParseData (pl, &len);
|
|
|
|
if (!str) {
|
|
return NULL;
|
|
} else {
|
|
item = PL_NewData (str, len);
|
|
item->line = pl->line;
|
|
return item;
|
|
}
|
|
}
|
|
|
|
case '"': {
|
|
int line = pl->line;
|
|
char *str = PL_ParseQuotedString (pl);
|
|
|
|
if (!str) {
|
|
return NULL;
|
|
} else {
|
|
return new_string (str, line);
|
|
}
|
|
}
|
|
|
|
default: {
|
|
int line = pl->line;
|
|
char *str = PL_ParseUnquotedString (pl);
|
|
|
|
if (!str) {
|
|
return NULL;
|
|
} else {
|
|
return new_string (str, line);
|
|
}
|
|
}
|
|
} // switch
|
|
}
|
|
|
|
VISIBLE plitem_t *
|
|
PL_GetPropertyList (const char *string)
|
|
{
|
|
pldata_t *pl = calloc (1, sizeof (pldata_t));
|
|
plitem_t *newpl = NULL;
|
|
|
|
if (!quotable_bitmap[0])
|
|
init_quotables ();
|
|
|
|
pl->ptr = string;
|
|
pl->pos = 0;
|
|
pl->end = strlen (string);
|
|
pl->error = NULL;
|
|
pl->line = 1;
|
|
|
|
if ((newpl = PL_ParsePropertyListItem (pl))) {
|
|
free (pl);
|
|
return newpl;
|
|
} else {
|
|
if (pl && pl->error && pl->error[0])
|
|
Sys_Printf ("plist: %d,%d: %s", pl->line, pl->pos, pl->error);
|
|
free (pl);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_tabs (dstring_t *dstr, int num)
|
|
{
|
|
char *tabs = dstring_reservestr (dstr, num);
|
|
|
|
memset (tabs, '\t', num);
|
|
tabs[num] = 0;
|
|
}
|
|
|
|
static void
|
|
write_string_len (dstring_t *dstr, const char *str, int len)
|
|
{
|
|
char *dst = dstring_reservestr (dstr, len);
|
|
memcpy (dst, str, len);
|
|
dst[len] = 0;
|
|
}
|
|
|
|
static char
|
|
to_hex (byte b)
|
|
{
|
|
char c = (b & 0xf) + '0';
|
|
if (c > '9')
|
|
c = c - '0' + 'A';
|
|
return c;
|
|
}
|
|
|
|
static void
|
|
write_binary (dstring_t *dstr, byte *binary, int len)
|
|
{
|
|
int i;
|
|
char *dst = dstring_reservestr (dstr, len * 2);
|
|
for (i = 0; i < len; i++) {
|
|
*dst++ = to_hex (binary[i] >> 4);
|
|
*dst++ = to_hex (binary[i]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_string (dstring_t *dstr, const char *str)
|
|
{
|
|
const char *s;
|
|
int len = 0;
|
|
char *dst;
|
|
int quoted = 0;
|
|
|
|
for (s = str; *s; s++) {
|
|
if (is_quotable (*s))
|
|
quoted = 1;
|
|
len++;
|
|
}
|
|
if (!quoted) {
|
|
dst = dstring_reservestr (dstr, len);
|
|
strcpy (dst, str);
|
|
return;
|
|
}
|
|
// assume worst case of all octal chars plus two quotes.
|
|
dst = dstring_reservestr (dstr, len * 4 + 2);
|
|
*dst++= '\"';
|
|
while (*str) {
|
|
if (*str && isascii ((byte) *str) && isprint ((byte) *str)
|
|
&& *str != '\\' && *str != '\"') {
|
|
*dst++ = *str++;
|
|
continue;
|
|
}
|
|
if (*str) {
|
|
*dst++ = '\\';
|
|
switch (*str) {
|
|
case '\"':
|
|
case '\\':
|
|
*dst++ = *str;
|
|
break;
|
|
case '\n':
|
|
*dst++ = 'n';
|
|
break;
|
|
case '\a':
|
|
*dst++ = 'a';
|
|
break;
|
|
case '\b':
|
|
*dst++ = 'b';
|
|
break;
|
|
case '\f':
|
|
*dst++ = 'f';
|
|
break;
|
|
case '\r':
|
|
*dst++ = 'r';
|
|
break;
|
|
case '\t':
|
|
*dst++ = 't';
|
|
break;
|
|
case '\v':
|
|
*dst++ = 'v';
|
|
break;
|
|
default:
|
|
*dst++ = '0' + ((((byte) *str) >> 6) & 3);
|
|
*dst++ = '0' + ((((byte) *str) >> 3) & 7);
|
|
*dst++ = '0' + (((byte) *str) & 7);
|
|
break;
|
|
}
|
|
str++;
|
|
}
|
|
}
|
|
*dst++ = '\"';
|
|
*dst++ = 0;
|
|
dstr->size = dst - dstr->str;
|
|
}
|
|
|
|
static void
|
|
write_item (dstring_t *dstr, plitem_t *item, int level)
|
|
{
|
|
void **list, **l;
|
|
dictkey_t *current;
|
|
plarray_t *array;
|
|
plbinary_t *binary;
|
|
int i;
|
|
|
|
switch (item->type) {
|
|
case QFDictionary:
|
|
write_string_len (dstr, "{\n", 2);
|
|
l = list = Hash_GetList ((hashtab_t *) item->data);
|
|
while ((current = (dictkey_t *) *l++)) {
|
|
write_tabs (dstr, level + 1);
|
|
write_string (dstr, current->key);
|
|
write_string_len (dstr, " = ", 3);
|
|
write_item (dstr, current->value, level + 1);
|
|
write_string_len (dstr, ";\n", 2);
|
|
}
|
|
free (list);
|
|
write_tabs (dstr, level);
|
|
write_string_len (dstr, "}", 1);
|
|
break;
|
|
case QFArray:
|
|
write_string_len (dstr, "(\n", 2);
|
|
array = (plarray_t *) item->data;
|
|
for (i = 0; i < array->numvals; i++) {
|
|
write_tabs (dstr, level + 1);
|
|
write_item (dstr, array->values[i], level + 1);
|
|
if (i < array->numvals - 1)
|
|
write_string_len (dstr, ",\n", 2);
|
|
}
|
|
write_string_len (dstr, "\n", 1);
|
|
write_tabs (dstr, level);
|
|
write_string_len (dstr, ")", 1);
|
|
break;
|
|
case QFBinary:
|
|
write_string_len (dstr, "<", 1);
|
|
binary = (plbinary_t *) item->data;
|
|
write_binary (dstr, binary->data, binary->size);
|
|
write_string_len (dstr, ">", 1);
|
|
break;
|
|
case QFString:
|
|
write_string (dstr, item->data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
VISIBLE char *
|
|
PL_WritePropertyList (plitem_t *pl)
|
|
{
|
|
dstring_t *dstr = dstring_newstr ();
|
|
|
|
if (!quotable_bitmap[0])
|
|
init_quotables ();
|
|
write_item (dstr, pl, 0);
|
|
write_string_len (dstr, "\n", 1);
|
|
return dstring_freeze (dstr);
|
|
}
|
|
|
|
VISIBLE pltype_t
|
|
PL_Type (plitem_t *item)
|
|
{
|
|
return item->type;
|
|
}
|
|
|
|
VISIBLE int
|
|
PL_Line (plitem_t *item)
|
|
{
|
|
return item->line;
|
|
}
|
|
|
|
VISIBLE void
|
|
PL_Message (plitem_t *messages, const plitem_t *item, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
dstring_t *string;
|
|
|
|
string = dstring_new ();
|
|
|
|
va_start (args, fmt);
|
|
dvsprintf (string, fmt, args);
|
|
va_end (args);
|
|
|
|
if (item) {
|
|
PL_A_AddObject (messages,
|
|
PL_NewString (va ("%d: %s", item->line, string->str)));
|
|
} else {
|
|
PL_A_AddObject (messages,
|
|
PL_NewString (va ("internal: %s", string->str)));
|
|
}
|
|
dstring_delete (string);
|
|
}
|
|
|
|
static int
|
|
pl_default_parser (const plfield_t *field, const plitem_t *item, void *data,
|
|
plitem_t *messages)
|
|
{
|
|
switch (field->type) {
|
|
case QFDictionary:
|
|
{
|
|
*(hashtab_t **)data = (hashtab_t *)item->data;
|
|
}
|
|
return 1;
|
|
case QFArray:
|
|
{
|
|
plarray_t *array = (plarray_t *)item->data;
|
|
typedef struct DARRAY_TYPE (plitem_t *) arraydata_t;
|
|
arraydata_t *arraydata = DARRAY_ALLOCFIXED(arraydata_t,
|
|
array->numvals,
|
|
malloc);
|
|
memcpy (arraydata->a, array->values,
|
|
array->numvals * sizeof (arraydata->a[0]));
|
|
}
|
|
return 1;
|
|
case QFBinary:
|
|
{
|
|
plbinary_t *binary = (plbinary_t *)item->data;
|
|
typedef struct DARRAY_TYPE (byte) bindata_t;
|
|
bindata_t *bindata = DARRAY_ALLOCFIXED(bindata_t,
|
|
binary->size, malloc);
|
|
memcpy (bindata->a, binary->data, binary->size);
|
|
*(bindata_t **)data = bindata;
|
|
}
|
|
return 1;
|
|
case QFString:
|
|
*(char **)data = (char *)item->data;
|
|
return 1;
|
|
}
|
|
PL_Message (messages, 0, "invalid item type: %d", field->type);
|
|
return 0;
|
|
}
|
|
|
|
VISIBLE int
|
|
PL_ParseDictionary (const plfield_t *fields, const plitem_t *dict, void *data,
|
|
plitem_t *messages)
|
|
{
|
|
void **list, **l;
|
|
dictkey_t *current;
|
|
int result;
|
|
plparser_t parser;
|
|
|
|
if (dict->type != QFDictionary) {
|
|
PL_Message (messages, dict, "error: not a dictionary object");
|
|
return 0;
|
|
}
|
|
|
|
if (!(l = list = Hash_GetList ((hashtab_t *) dict->data))) {
|
|
// empty struct: leave as default
|
|
return 1;
|
|
}
|
|
|
|
while ((current = (dictkey_t *) *l++)) {
|
|
const plfield_t *f;
|
|
for (f = fields; f->name; f++) {
|
|
if (strcmp (f->name, current->key) == 0) {
|
|
plitem_t *item = current->value;
|
|
void *flddata = (byte *)data + f->offset;
|
|
|
|
if (f->parser) {
|
|
parser = f->parser;
|
|
} else {
|
|
parser = pl_default_parser;
|
|
}
|
|
if (item->type != f->type) {
|
|
PL_Message (messages, item, "error: %s is the wrong type"
|
|
" Got %s, expected %s",current->key,
|
|
pl_types[f->type],
|
|
pl_types[item->type]);
|
|
result = 0;
|
|
} else {
|
|
if (!parser (f, item, flddata, messages)) {
|
|
result = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!f->name) {
|
|
PL_Message (messages, dict, "error: unknown field %s",
|
|
current->key);
|
|
result = 0;
|
|
}
|
|
}
|
|
free (list);
|
|
return result;
|
|
}
|
|
|
|
VISIBLE int
|
|
PL_ParseArray (const plfield_t *field, const plitem_t *array, void *data,
|
|
plitem_t *messages)
|
|
{
|
|
int result;
|
|
plparser_t parser;
|
|
plarray_t *plarray = (plarray_t *) array->data;
|
|
plelement_t *element = (plelement_t *) field->data;
|
|
typedef struct arr_s DARRAY_TYPE(byte) arr_t;
|
|
arr_t *arr;
|
|
plfield_t f = { 0, 0, element->type, element->parser, element->data };
|
|
|
|
if (array->type != QFArray) {
|
|
PL_Message (messages, array, "error: not an array object");
|
|
return 0;
|
|
}
|
|
if (f.parser) {
|
|
parser = f.parser;
|
|
} else {
|
|
parser = pl_default_parser;
|
|
}
|
|
|
|
arr = DARRAY_ALLOCFIXED (arr_t, plarray->numvals * element->stride,
|
|
element->alloc);
|
|
memset (arr->a, 0, arr->size);
|
|
// the array is allocated using bytes, but need the actual number of
|
|
// elements in the array
|
|
arr->size = arr->maxSize = plarray->numvals;
|
|
|
|
for (int i = 0; i < plarray->numvals; i++) {
|
|
plitem_t *item = plarray->values[i];
|
|
void *eledata = &arr->a[i * element->stride];
|
|
|
|
if (item->type != element->type) {
|
|
PL_Message (messages, item,
|
|
"error: element %d is the wrong type"
|
|
" Got %s, expected %s", i,
|
|
pl_types[element->type],
|
|
pl_types[item->type]);
|
|
result = 0;
|
|
} else {
|
|
if (!parser (&f, item, eledata, messages)) {
|
|
result = 0;
|
|
}
|
|
}
|
|
}
|
|
*(arr_t **) data = arr;
|
|
return result;
|
|
}
|