quakeforge/libs/util/plist.c
Bill Currie 7b9dc3c878 [plist] Add support for parsing and writing JSON
It seemed like a good idea since vulkan and gltf resources use JSON.
However, plist and json parsing and writing are separate: there's no
auto-detection for parsing, and the appropriate function must be used
for writing (though reading one then writing the other will work, but
may result in some information loss, or even invalid json (binary)).

Escape characters aren't handled quite to spec yet (eg, no \uxxxx).

The tests are pretty lame, but they're taken from rfc7159 and round-trip
correctly (which is a surprise for the fp numbers).
2024-09-14 18:59:48 +09:00

1815 lines
38 KiB
C

/*
plist.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/plist.h"
#include "QF/qtypes.h"
#include "QF/sys.h"
/*
Generic property list item.
*/
struct plitem_s {
pltype_t type;
unsigned users;
union {
void *data;
double number;
bool boolean;
};
void *user_data;
int line;
};//plitem_t
/*
Dictionaries
*/
struct dictkey_s {
char *key;
plitem_t *value;
};
typedef struct dictkey_s dictkey_t;
struct pldict_s {
hashtab_t *tab;
struct DARRAY_TYPE (dictkey_t *) keys;
};
typedef struct pldict_s pldict_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 end;
unsigned pos;
unsigned line;
unsigned line_start;
bool json;
dstring_t *errmsg;
hashctx_t **hashctx;
} 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",
"binary",
"string",
"number",
"bool",
"null",
};
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 *pl);
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_Release (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 (hashctx_t **hashctx)
{
plitem_t *item = pl_newitem (QFDictionary);
pldict_t *dict = malloc (sizeof (pldict_t));
dict->tab = Hash_NewTable (1021, dict_get_key, dict_free, nullptr, hashctx);
DARRAY_INIT (&dict->keys, 8);
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 (const char *str, size_t len, pldata_t *pl)
{
plitem_t *item = pl_newitem (QFString);
item->data = malloc (len + 1);
memcpy (item->data, str, len);
((char *) item->data)[len] = 0;
item->line = pl ? pl->line : 0;
return item;
}
VISIBLE plitem_t *
PL_NewString (const char *str)
{
return new_string (str, strlen (str), 0);
}
VISIBLE plitem_t *
PL_Retain (plitem_t *item)
{
if (item) {
item->users++;
}
return item;
}
VISIBLE plitem_t *
PL_Release (plitem_t *item)
{
pldict_t *dict;
plarray_t *array;
if (!item || (item->users && --item->users > 0)) {
return item;
}
switch (item->type) {
case QFDictionary:
dict = item->data;
Hash_DelTable (dict->tab);
DARRAY_CLEAR (&dict->keys);
free (item->data);
break;
case QFArray:
{
array = item->data;
int i = array->numvals;
while (i-- > 0) {
PL_Release (array->values[i]);
}
free (array->values);
free (item->data);
}
break;
case QFBinary:
free (((plbinary_t *) item->data)->data);
free (item->data);
break;
case QFString:
free (item->data);
break;
case QFNumber:
case QFBool:
case QFNull:
case QFMultiType:
break;
}
free (item);
return 0;
}
VISIBLE void
PL_SetUserData (plitem_t *item, void *data)
{
item->user_data = data;
}
VISIBLE void *
PL_GetUserData (plitem_t *item)
{
return item->user_data;
}
VISIBLE size_t
PL_BinarySize (const plitem_t *binary)
{
if (!binary || binary->type != QFBinary) {
return 0;
}
plbinary_t *bin = (plbinary_t *) binary->data;
return bin->size;
}
VISIBLE const void *
PL_BinaryData (const plitem_t *binary)
{
if (!binary || binary->type != QFBinary) {
return 0;
}
plbinary_t *bin = (plbinary_t *) binary->data;
return bin->data;
}
VISIBLE const char *
PL_String (const plitem_t *string)
{
if (!string || string->type != QFString) {
return nullptr;
}
return string->data;
}
VISIBLE double
PL_Number (const plitem_t *number)
{
if (!number || number->type != QFNumber) {
return 0;
}
return number->number;
}
VISIBLE bool
PL_Bool (const plitem_t *boolean)
{
if (!boolean || boolean->type != QFBool) {
return false;
}
return boolean->boolean;
}
VISIBLE plitem_t *
PL_ObjectForKey (const plitem_t *item, const char *key)
{
if (!item || item->type != QFDictionary) {
return nullptr;
}
pldict_t *dict = (pldict_t *) item->data;
dictkey_t *k = (dictkey_t *) Hash_Find (dict->tab, key);
return k ? k->value : nullptr;
}
VISIBLE const char *
PL_KeyAtIndex (const plitem_t *item, int index)
{
if (!item || item->type != QFDictionary) {
return nullptr;
}
pldict_t *dict = (pldict_t *) item->data;
if (index < 0 || (size_t) index >= dict->keys.size) {
return nullptr;
}
return dict->keys.a[index]->key;
}
VISIBLE void
PL_RemoveObjectForKey (plitem_t *item, const char *key)
{
if (!item || item->type != QFDictionary) {
return;
}
pldict_t *dict = (pldict_t *) item->data;
dictkey_t *k;
plitem_t *value;
k = (dictkey_t *) Hash_Del (dict->tab, key);
if (!k) {
return;
}
value = k->value;
k->value = 0;
for (size_t i = 0; i < dict->keys.size; i++) {
if (dict->keys.a[i] == k) {
DARRAY_REMOVE_AT (&dict->keys, i);
break;
}
}
dict_free (k, 0);
PL_Release (value);
}
VISIBLE plitem_t *
PL_D_AllKeys (const plitem_t *item)
{
if (!item || item->type != QFDictionary) {
return nullptr;
}
pldict_t *dict = (pldict_t *) item->data;
dictkey_t *current;
plitem_t *array;
if (!(array = PL_NewArray ()))
return nullptr;
for (size_t i = 0; i < dict->keys.size; i++) {
current = dict->keys.a[i];
PL_A_AddObject (array, PL_NewString (current->key));
}
return array;
}
VISIBLE int
PL_D_NumKeys (const plitem_t *item)
{
if (!item || item->type != QFDictionary) {
return 0;
}
pldict_t *dict = (pldict_t *) item->data;
return Hash_NumElements (dict->tab);
}
VISIBLE plitem_t *
PL_ObjectAtIndex (const plitem_t *array, int index)
{
if (!array || array->type != QFArray) {
return nullptr;
}
plarray_t *arr = (plarray_t *) array->data;
return index >= 0 && index < arr->numvals ? arr->values[index] : nullptr;
}
VISIBLE bool
PL_D_AddObject (plitem_t *item, const char *key, plitem_t *value)
{
if (!item || item->type != QFDictionary || !value) {
return false;
}
pldict_t *dict = (pldict_t *) item->data;
dictkey_t *k;
if ((k = Hash_Find (dict->tab, key))) {
PL_Retain (value);
PL_Release (k->value);
k->value = value;
} else {
k = malloc (sizeof (dictkey_t));
if (!k)
return false;
PL_Retain (value);
k->key = strdup (key);
k->value = value;
Hash_Add (dict->tab, k);
DARRAY_APPEND (&dict->keys, k);
}
return true;
}
VISIBLE bool
PL_D_Extend (plitem_t *dstDict, plitem_t *srcDict)
{
if (!dstDict || dstDict->type != QFDictionary
|| !srcDict || srcDict->type != QFDictionary
|| ((pldict_t *) srcDict->data)->keys.size < 1) {
return false;
}
pldict_t *dst = dstDict->data;
pldict_t *src = srcDict->data;
size_t count = dst->keys.size;
DARRAY_RESIZE (&dst->keys, dst->keys.size + src->keys.size);// open space
DARRAY_RESIZE (&dst->keys, count); // put size back so it's correct
for (size_t i = 0; i < src->keys.size; i++) {
dictkey_t *key = src->keys.a[i];
dictkey_t *k;
if ((k = Hash_Find (dst->tab, key->key))) {
PL_Retain (key->value);
PL_Release (k->value);
k->value = key->value;
} else {
k = malloc (sizeof (dictkey_t));
if (!k)
return false;
PL_Retain (key->value);
k->key = strdup (key->key);
k->value = key->value;
Hash_Add (dst->tab, k);
DARRAY_APPEND (&dst->keys, k);
}
}
return true;
}
static bool
check_array_size (plarray_t *arr, int count)
{
if (count > arr->maxvals) {
int newmax = (count + 127) & ~127;
int size = newmax * sizeof (plitem_t *);
plitem_t **tmp = realloc (arr->values, size);
if (!tmp)
return false;
arr->maxvals = newmax;
arr->values = tmp;
}
return true;
}
VISIBLE bool
PL_A_InsertObjectAtIndex (plitem_t *array, plitem_t *item, int index)
{
if (!array || array->type != QFArray || !item) {
return false;
}
plarray_t *arr;
arr = (plarray_t *)array->data;
if (!check_array_size (arr, arr->numvals + 1)) {
return false;
}
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 *));
PL_Retain (item);
arr->values[index] = item;
arr->numvals++;
return true;
}
VISIBLE bool
PL_A_AddObject (plitem_t *array, plitem_t *item)
{
return PL_A_InsertObjectAtIndex (array, item, -1);
}
VISIBLE bool
PL_A_Extend (plitem_t *dstArray, plitem_t *srcArray)
{
if (!dstArray || dstArray->type != QFArray
|| !srcArray || srcArray->type != QFArray
|| ((plarray_t *) srcArray->data)->numvals < 1) {
return false;
}
plarray_t *dst = dstArray->data;
plarray_t *src = srcArray->data;
if (!check_array_size (dst, dst->numvals + src->numvals)) {
return false;
}
for (int i = 0; i < src->numvals; i++) {
PL_Retain (src->values[i]);
dst->values[dst->numvals++] = src->values[i];
}
return true;
}
VISIBLE int
PL_A_NumObjects (const plitem_t *array)
{
if (!array || array->type != QFArray) {
return 0;
}
return ((plarray_t *) array->data)->numvals;
}
VISIBLE void
PL_RemoveObjectAtIndex (plitem_t *array, int index)
{
if (!array || array->type != QFArray) {
return;
}
plarray_t *arr;
plitem_t *item;
arr = (plarray_t *)array->data;
if (index < 0 || index >= arr->numvals)
return;
item = arr->values[index];
arr->numvals--;
while (index < arr->numvals) {
arr->values[index] = arr->values[index + 1];
index++;
}
PL_Release (item);
}
static plitem_t * __attribute__((format(PRINTF, 2, 3)))
pl_error (pldata_t *pl, const char *fmt, ...)
{
if (!pl->errmsg) {
pl->errmsg = dstring_new ();
}
va_list args;
va_start (args, fmt);
dvsprintf (pl->errmsg, fmt, args);
va_end (args);
return nullptr;
}
static bool
pl_skipspace (pldata_t *pl, bool end_ok)
{
if (pl->json) {
while (pl->pos < pl->end) {
byte c = pl->ptr[pl->pos];
if (c > ' ') {
return true;
}
if (c != ' ' && c != '\n' && c != '\r' && c != '\t') {
pl_error (pl, "Invalid character 0x%02x", c);
return false;
}
if (c == '\n') {
pl->line++;
pl->line_start = pl->pos + 1;
}
pl->pos++;
}
} else {
while (pl->pos < pl->end) {
byte c = pl->ptr[pl->pos];
if (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) {
// end of string in a single-line comment is always
// an error
pl_error (pl, "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++;
pl->line_start = pl->pos + 1;
} else if (c == '*' && pl->pos < pl->end - 1
&& pl->ptr[pl->pos+1] == '/') {
pl->pos++;
break;
}
pl->pos++;
}
if (pl->pos >= pl->end) {
// end of string in a multi-line comment is always
// an error
pl_error (pl, "Reached end of string in comment");
return false;
}
} else {
return true;
}
} else {
return true;
}
}
if (c < ' ' && c != '\n' && c != '\r' && c != '\t') {
pl_error (pl, "Invalid character 0x%02x", c);
return false;
}
if (c == '\n') {
pl->line++;
pl->line_start = pl->pos + 1;
}
pl->pos++;
}
}
if (!end_ok) {
pl_error (pl, "Reached end of string");
}
return false;
}
static int
pl_checknext (pldata_t *pl, const char *valid, bool end_ok)
{
if (!pl_skipspace (pl, end_ok)) {
return end_ok;
}
char ch = pl->ptr[pl->pos];
if (strchr (valid, ch)) {
return 1;
}
size_t len = strlen (valid);
size_t size = 3 + (strlen (valid) - 1) * 7 + 1;
char expected[size], *p = expected;
p[0] = '\'';
p[1] = valid[0];
p[2] = '\'';
p += 3;
for (size_t i = 1; i < len; i++, p++) {
memcpy (p, " or 'x'", 7);
p[5] = valid[i];
}
p[0] = 0;
pl_error (pl, "Unexpected character %c (wanted %s)", ch, expected);
return 0;
}
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 int
pl_parsekeyvalue (pldata_t *pl, plitem_t *dict, bool end_ok)
{
plitem_t *key = 0;
plitem_t *value = 0;
if (!(key = pl_parsepropertylistitem (pl))) {
return 0;
}
if (key->type != QFString) {
pl_error (pl, "Key is not a string");
goto error;
}
if (!pl_checknext (pl, pl->json ? ":" : "=", 0)) {
goto error;
}
pl->pos++;
if (!(value = pl_parsepropertylistitem (pl))) {
goto error;
}
if (!PL_D_AddObject (dict, PL_String (key), value)) {
goto error;
}
PL_Release (key); // don't need the key item
const char *next = pl->json ? (end_ok ? "," : ",}")
: (end_ok ? ";" : ";}");
if (!pl_checknext (pl, next, end_ok)) {
return 0;
}
if (pl->ptr[pl->pos] == next[0]) {
pl->pos++;
}
return 1;
error:
PL_Release (key);
PL_Release (value);
return 0;
}
static plitem_t *
pl_parsedictionary (pldata_t *pl)
{
plitem_t *dict = PL_NewDictionary (pl->hashctx);
dict->line = pl->line;
pl->pos++; // skip over opening {
while (pl_skipspace (pl, false) && pl->ptr[pl->pos] != '}') {
if (!pl_parsekeyvalue (pl, dict, false)) {
PL_Release (dict);
return nullptr;
}
}
if (pl->pos >= pl->end) {
pl_error (pl, "Unexpected end of string when parsing dictionary");
PL_Release (dict);
return nullptr;
}
pl->pos++; // skip over closing }
return dict;
}
static int
pl_parsevalue (pldata_t *pl, plitem_t *array, int end_ok)
{
plitem_t *value;
if (!(value = pl_parsepropertylistitem (pl))) {
return 0;
}
if (!PL_A_AddObject (array, value)) {
pl_error (pl, "too many items in array");
PL_Release (value);
return 0;
}
const char *next = pl->json ? (end_ok ? "," : ",]")
: (end_ok ? "," : ",)");
if (!pl_checknext (pl, next, end_ok)) {
return 0;
}
if (pl->ptr[pl->pos] == ',') {
pl->pos++;
}
return 1;
}
static plitem_t *
pl_parsearray (pldata_t *pl)
{
plitem_t *array = PL_NewArray ();
array->line = pl->line;
pl->pos++; // skip over opening (
while (pl_skipspace (pl, false) && pl->ptr[pl->pos] != ')') {
if (!pl_parsevalue (pl, array, 0)) {
PL_Release (array);
return nullptr;
}
}
if (pl->pos >= pl->end) {
pl_error (pl, "Unexpected end of string when parsing array");
PL_Release (array);
return nullptr;
}
pl->pos++; // skip over opening )
return array;
}
static plitem_t *
pl_parsebinary (pldata_t *pl)
{
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 (pl, "invalid data, missing nibble");
return nullptr;
}
int 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]);
}
plitem_t *item = PL_NewData (str, len);
item->line = pl->line;
return item;
}
pl_error (pl, "invalid character in data: %02x", c);
return nullptr;
}
pl_error (pl, "Reached end of string while parsing data");
return nullptr;
}
static plitem_t *
pl_parsequotedstring (pldata_t *pl)
{
unsigned int start = ++pl->pos;
unsigned int escaped = 0;
unsigned int shrink = 0;
bool hex = false;
bool long_string = false;
plitem_t *str;
if (!pl->json
&& 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->line_start = pl->pos + 1;
}
pl->pos++;
}
if (pl->pos >= pl->end) {
pl_error (pl, "Reached end of string while parsing quoted string");
return nullptr;
}
if (pl->pos - start - shrink == 0) {
str = new_string ("", 0, pl);
} 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 = new_string (chars, pl->pos - start - shrink, pl);
}
if (long_string)
pl->pos += 2;
pl->pos++;
return str;
}
static plitem_t *
pl_parseunquotedstring (pldata_t *pl)
{
unsigned int start = pl->pos;
while (pl->pos < pl->end) {
if (is_quotable (pl->ptr[pl->pos]))
break;
pl->pos++;
}
return new_string (&pl->ptr[start], pl->pos - start, pl);
}
static plitem_t *
pl_parsepropertylistitem (pldata_t *pl)
{
if (!pl_skipspace (pl, false)) {
return nullptr;
}
switch (pl->ptr[pl->pos]) {
case '{': return pl_parsedictionary (pl);
case '(': return pl_parsearray (pl);
case '<': return pl_parsebinary (pl);
case '"': return pl_parsequotedstring (pl);
default: return pl_parseunquotedstring (pl);
}
}
static plitem_t *
pl_parsejson_array (pldata_t *pl)
{
plitem_t *array = PL_NewArray ();
array->line = pl->line;
pl->pos++; // skip over opening [
while (pl_skipspace (pl, false) && pl->ptr[pl->pos] != ']') {
if (!pl_parsevalue (pl, array, false)) {
PL_Release (array);
return nullptr;
}
}
if (pl->pos >= pl->end) {
pl_error (pl, "Unexpected end of string when parsing array");
PL_Release (array);
return nullptr;
}
pl->pos++; // skip over opening )
return array;
}
static unsigned
pl_parsejson_literal (pldata_t *pl)
{
unsigned start = pl->pos;
while (pl->pos < pl->end) {
char c = pl->ptr[pl->pos];
if (!isdigit (c) && c != '-' && c != '.' && (c < 'a' || c > 'z')) {
break;
}
pl->pos++;
}
return pl->pos - start;
}
static plitem_t *
pl_parsejson_null (pldata_t *pl)
{
unsigned start = pl->pos;
unsigned len = pl_parsejson_literal (pl);
if (len == 4 && strncmp ("null", pl->ptr + start, len) == 0) {
return pl_newitem (QFNull);
} else {
return pl_error (pl, "Invalid literal: %.*s", len, pl->ptr + start);
}
}
static plitem_t *
pl_parsejson_bool (pldata_t *pl)
{
unsigned start = pl->pos;
unsigned len = pl_parsejson_literal (pl);
if (len == 4 && strncmp ("true", pl->ptr + start, len) == 0) {
auto item = pl_newitem (QFBool);
item->boolean = true;
return item;
} else if (len == 5 && strncmp ("false", pl->ptr + start, len) == 0) {
auto item = pl_newitem (QFBool);
item->boolean = false;
return item;
} else {
return pl_error (pl, "Invalid literal: %.*s", len, pl->ptr + start);
}
}
static plitem_t *
pl_parsejson_number (pldata_t *pl)
{
unsigned start = pl->pos;
unsigned len = pl_parsejson_literal (pl);
char str[len + 1];
char *end = nullptr;
memcpy (str, pl->ptr + start, len);
str[len] = 0;
double val = strtod (str, &end);
if (*end) {
return pl_error (pl, "Invalid literal: %.*s", len, pl->ptr + start);
}
auto item = pl_newitem (QFNumber);
item->number = val;
return item;
}
static plitem_t *
pl_parsejson_element (pldata_t *pl)
{
if (!pl_skipspace (pl, false)) {
return nullptr;
}
switch (pl->ptr[pl->pos]) {
case '[': return pl_parsejson_array (pl);
case '{': return pl_parsedictionary (pl);
case ']':
case '}':
case ':':
case ',':
return pl_error (pl, "Unexpected character %c", pl->ptr[pl->pos]);
case '"': return pl_parsequotedstring (pl);
case 'n':
return pl_parsejson_null (pl);
case 't':
case 'f':
return pl_parsejson_bool (pl);
default:
if (pl->ptr[pl->pos] == '-' || isdigit (pl->ptr[pl->pos])) {
return pl_parsejson_number (pl);
}
return pl_error (pl, "Invalid literal");
}
}
static plitem_t *
pl_parseitem (const char *string, hashctx_t **hashctx,
plitem_t *(*parse) (pldata_t *), bool json)
{
plitem_t *newpl = nullptr;
if (!quotable_bitmap[0])
init_quotables ();
pldata_t pl = {
.ptr = string,
.end = strlen (string),
.line = 1,
.json = json,
.hashctx = hashctx,
};
if (!(newpl = parse (&pl))) {
if (pl.errmsg) {
Sys_Printf ("plist: %d,%d: %s\n", pl.line, pl.pos - pl.line_start,
pl.errmsg->str);
dstring_delete (pl.errmsg);
}
return nullptr;
}
return newpl;
}
VISIBLE plitem_t *
PL_GetPropertyList (const char *string, hashctx_t **hashctx)
{
return pl_parseitem (string, hashctx, pl_parsepropertylistitem, false);
}
VISIBLE plitem_t *
PL_ParseJSON (const char *string, hashctx_t **hashctx)
{
return pl_parseitem (string, hashctx, pl_parsejson_element, true);
}
static plitem_t *
pl_getdictionary (pldata_t *pl)
{
plitem_t *dict = PL_NewDictionary (pl->hashctx);
dict->line = pl->line;
while (pl_skipspace (pl, true)) {
if (!pl_parsekeyvalue (pl, dict, true)) {
PL_Release (dict);
return nullptr;
}
}
return dict;
}
VISIBLE plitem_t *
PL_GetDictionary (const char *string, hashctx_t **hashctx)
{
return pl_parseitem (string, hashctx, pl_getdictionary, false);
}
static plitem_t *
pl_getarray (pldata_t *pl)
{
plitem_t *array = PL_NewArray ();
array->line = pl->line;
while (pl_skipspace (pl, true)) {
if (!pl_parsevalue (pl, array, true)) {
PL_Release (array);
return nullptr;
}
}
return array;
}
VISIBLE plitem_t *
PL_GetArray (const char *string, hashctx_t **hashctx)
{
return pl_parseitem (string, hashctx, pl_getarray, false);
}
static void
write_tabs (dstring_t *dstr, int num)
{
char *tabs = dstring_openstr (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_openstr (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_openstr (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, bool json)
{
const char *s;
int len = 0;
char *dst;
bool quoted = json;
if (!quoted) {
for (s = str; *s; s++) {
if (is_quotable (*s))
quoted = true;
len++;
}
}
if (!quoted) {
dst = dstring_openstr (dstr, len);
strcpy (dst, str);
return;
}
// assume worst case of all octal chars plus two quotes.
dst = dstring_openstr (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, const plitem_t *item, int level, bool json)
{
dictkey_t *current;
plarray_t *array;
pldict_t *dict;
plbinary_t *binary;
int i;
switch (item->type) {
case QFDictionary:
write_string_len (dstr, "{\n", 2);
dict = (pldict_t *) item->data;
for (size_t i = 0; i < dict->keys.size; i++) {
current = dict->keys.a[i];
write_tabs (dstr, level + 1);
write_string (dstr, current->key, json);
write_string_len (dstr, " = ", 3);
write_item (dstr, current->value, level + 1, json);
write_string_len (dstr, ";\n", 2);
}
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, json);
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, json);
break;
case QFNumber:
dasprintf (dstr, "%.17g", item->number);
break;
case QFBool:
dstring_appendstr (dstr, item->boolean ? "true" : "false");
break;
case QFNull:
dstring_appendstr (dstr, "null");
break;
case QFMultiType:
break;
}
}
VISIBLE char *
PL_WritePropertyList (const plitem_t *pl)
{
dstring_t *dstr = dstring_newstr ();
if (pl) {
if (!quotable_bitmap[0])
init_quotables ();
write_item (dstr, pl, 0, false);
write_string_len (dstr, "\n", 1);
}
return dstring_freeze (dstr);
}
VISIBLE char *
PL_WriteJSON (const plitem_t *pl)
{
dstring_t *dstr = dstring_newstr ();
if (pl) {
if (!quotable_bitmap[0])
init_quotables ();
write_item (dstr, pl, 0, true);
write_string_len (dstr, "\n", 1);
}
return dstring_freeze (dstr);
}
VISIBLE pltype_t
PL_Type (const plitem_t *item)
{
return item->type;
}
VISIBLE int
PL_Line (const 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 *va_str;
dstring_t *msg_str;
char *msg;
va_str = dstring_new ();
msg_str = dstring_new ();
va_start (args, fmt);
dvsprintf (va_str, fmt, args);
va_end (args);
if (item) {
msg = dsprintf (msg_str, "%d: %s", item->line, va_str->str);
} else {
msg = dsprintf (msg_str, "internal: %s", va_str->str);
}
PL_A_AddObject (messages, PL_NewString (msg));
dstring_delete (va_str);
dstring_delete (msg_str);
}
static int
pl_default_parser (const plfield_t *field, const plitem_t *item, void *data,
plitem_t *messages, void *context)
{
switch (field->type) {
case QFDictionary:
{
*(hashtab_t **)data = ((pldict_t *)item->data)->tab;
}
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;
case QFNumber:
*(double *)data = item->number;
return 1;
case QFBool:
*(bool *)data = item->boolean;
return 1;
case QFNull:
// needs special handling, so not valid for default parsing
break;
case QFMultiType:
break;
}
PL_Message (messages, 0, "invalid item type: %d", field->type);
return 0;
}
VISIBLE int
PL_CheckType (pltype_t field_type, pltype_t item_type)
{
if (field_type & QFMultiType) {
// field_type is a mask of allowed types
return field_type & (1 << item_type);
} else {
// exact match
return field_type == item_type;
}
}
VISIBLE void
PL_TypeMismatch (plitem_t *messages, const plitem_t *item, const char *name,
pltype_t field_type, pltype_t item_type)
{
const int num_types = sizeof (pl_types) / sizeof (pl_types[0]);
if (field_type & QFMultiType) {
PL_Message (messages, item,
"error: %s is the wrong type. Got %s, expected one of:",
name, pl_types[item_type]);
field_type &= ~QFMultiType;
for (int type = 0; field_type && type < num_types;
type++, field_type >>= 1) {
if (field_type & 1) {
PL_Message (messages, item, " %s", pl_types[type]);
}
}
} else {
PL_Message (messages, item,
"error: %s is the wrong type. Got %s, expected %s", name,
pl_types[item_type], pl_types[field_type]);
}
}
VISIBLE int
PL_ParseStruct (const plfield_t *fields, const plitem_t *item, void *data,
plitem_t *messages, void *context)
{
pldict_t *dict = item->data;
dictkey_t *current;
int result = 1;
plparser_t parser;
if (item->type != QFDictionary) {
PL_Message (messages, item, "error: not a dictionary object");
return 0;
}
for (size_t i = 0; i < dict->keys.size; i++) {
const plfield_t *f;
current = dict->keys.a[i];
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 (!PL_CheckType (f->type, item->type)) {
PL_TypeMismatch (messages, item, current->key,
f->type, item->type);
result = 0;
} else {
if (!parser (f, item, flddata, messages, context)) {
result = 0;
}
}
break;
}
}
if (!f->name) {
PL_Message (messages, item, "error: unknown field '%s'",
current->key);
result = 0;
}
}
return result;
}
VISIBLE int
PL_ParseArray (const plfield_t *field, const plitem_t *array, void *data,
plitem_t *messages, void *context)
{
int result = 1;
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_OBJ (arr_t, plarray->numvals * element->stride,
element->alloc, context);
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];
f.offset = i;
if (!PL_CheckType (element->type, item->type)) {
char index[16];
snprintf (index, sizeof(index) - 1, "%d", i);
index[15] = 0;
PL_TypeMismatch (messages, item, index, element->type, item->type);
result = 0;
} else {
if (!parser (&f, item, eledata, messages, context)) {
result = 0;
}
}
}
*(arr_t **) data = arr;
return result;
}
VISIBLE int
PL_ParseLabeledArray (const plfield_t *field, const plitem_t *item,
void *data, plitem_t *messages, void *context)
{
pldict_t *dict = item->data;
dictkey_t *current;
int result = 1;
plparser_t parser;
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 (item->type != QFDictionary) {
PL_Message (messages, item, "error: not a dictionary object");
return 0;
}
if (f.parser) {
parser = f.parser;
} else {
parser = pl_default_parser;
}
arr = DARRAY_ALLOCFIXED_OBJ (arr_t, dict->keys.size * element->stride,
element->alloc, context);
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 = dict->keys.size;
for (size_t i = 0; i < dict->keys.size; i++) {
current = dict->keys.a[i];
plitem_t *item = current->value;
void *eledata = &arr->a[i * element->stride];
f.name = current->key;
f.offset = i;
if (!PL_CheckType (element->type, item->type)) {
char index[16];
snprintf (index, sizeof(index) - 1, "%zd", i);
index[15] = 0;
PL_TypeMismatch (messages, item, index, element->type, item->type);
result = 0;
} else {
if (!parser (&f, item, eledata, messages, context)) {
result = 0;
}
}
}
*(arr_t **) data = arr;
return result;
}
VISIBLE int
PL_ParseSymtab (const plfield_t *field, const plitem_t *item, void *data,
plitem_t *messages, void *context)
{
pldict_t *dict = item->data;
dictkey_t *current;
int result = 1;
plparser_t parser;
__auto_type tab = (hashtab_t *) data;
plelement_t *element = (plelement_t *) field->data;
plfield_t f = { 0, 0, element->type, element->parser, element->data };
if (item->type != QFDictionary) {
PL_Message (messages, item, "error: not a dictionary object");
return 0;
}
if (f.parser) {
parser = f.parser;
} else {
PL_Message (messages, item, "no parser set");
return 0;
}
void *obj = element->alloc (context, element->stride);
memset (obj, 0, element->stride);
for (size_t i = 0; i < dict->keys.size; i++) {
current = dict->keys.a[i];
const char *key = current->key;
plitem_t *item = current->value;
if (!PL_CheckType (element->type, item->type)) {
PL_TypeMismatch (messages, item, key, element->type, item->type);
result = 0;
continue;
}
f.name = key;
if (Hash_Find (tab, key)) {
PL_Message (messages, item, "duplicate name");
result = 0;
} else {
if (!parser (&f, item, obj, messages, context)) {
result = 0;
} else {
Hash_Add (tab, obj);
obj = element->alloc (context, element->stride);
memset (obj, 0, element->stride);
}
}
}
Hash_Free (tab, obj);
return result;
}