mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-03-22 18:31:27 +00:00
[util] Add code to parse a dictionary to a struct
PL_ParseDictionary itself does only one level, but it takes care of the key-field mappings and property list item type checking leaving the actual parsing to a helper specified by the field. That helper is free to call PL_ParseDictionary recursively.
This commit is contained in:
parent
f6ea9e4d87
commit
8bd5f4f201
2 changed files with 208 additions and 0 deletions
|
@ -53,6 +53,52 @@ typedef enum {
|
|||
*/
|
||||
typedef struct plitem_s plitem_t;
|
||||
|
||||
struct plfield_s;
|
||||
/** Custom parser for the field.
|
||||
|
||||
With this, custom parsing of any property list object type is
|
||||
supported. For example, parsing of strings into numeric values,
|
||||
converting binary objects to images, and deeper parsing of array and
|
||||
dictionary objects.
|
||||
|
||||
If null, then the default parser for the object type is used:
|
||||
* QFString: the point to the actual string. The string continues
|
||||
to be owned by the string object.
|
||||
* QFBinary: pointer to fixed-size DARRAY_TYPE(byte) (so size isn't
|
||||
lost)
|
||||
* QFArray: pointer to fixed-size DARRAY_TYPE(plitem_t *) with the
|
||||
indivisual objects. The individual objects continue to be owned
|
||||
by the array object.
|
||||
* QFDictionary: pointer to the hashtab_t hash table used for the
|
||||
dictionary object. The hash table continues to be owned by the
|
||||
dictionary object.
|
||||
|
||||
\param field Pointer to this field item.
|
||||
\param item The property list item being parsed into the field.
|
||||
\param data Pointer to the field in the structure being parsed.
|
||||
\param messages An array object the parser can use to store any
|
||||
error messages. Messages should be strings, but no
|
||||
checking is done: it is up to the top-level caller to
|
||||
parse out the messages.
|
||||
\return 0 for error, 1 for success. See \a PL_ParseDictionary.
|
||||
*/
|
||||
typedef int (*plparser_t) (const struct plfield_s *field,
|
||||
const struct plitem_s *item,
|
||||
void *data,
|
||||
struct plitem_s *messages);
|
||||
|
||||
/** A field to be parsed from a dictionary item.
|
||||
|
||||
something
|
||||
*/
|
||||
typedef struct plfield_s {
|
||||
const char *name; ///< matched by dictionary key
|
||||
size_t offset; ///< the offset of the field within the structure
|
||||
pltype_t type; ///< the required type of the dictionary object
|
||||
plparser_t parser; ///< custom parser function
|
||||
void *data; ///< additional data for \a parser
|
||||
} plfield_t;
|
||||
|
||||
/** Create an in-memory representation of the contents of a property list.
|
||||
|
||||
\param string the saved plist, as read from a file.
|
||||
|
@ -234,6 +280,43 @@ plitem_t *PL_NewString (const char *str);
|
|||
*/
|
||||
void PL_Free (plitem_t *item);
|
||||
|
||||
/** Parse a dictionary object into a structure.
|
||||
|
||||
For each key in the dictionary, the corresponding field item is used to
|
||||
determine how to parse the object associated with that key. Duplicate
|
||||
field items are ignored: only the first item is used, and no checking is
|
||||
done. Fields for which there is no key in the dictionary are also ignored,
|
||||
and the destination is left unmodified. However, keys that have no
|
||||
corresponding field are treated as errors and a suitable message is added
|
||||
to the \a messages object.
|
||||
|
||||
When an error occurs (unknown key, incorrect item type (item type does not
|
||||
match the type specified in the field item) or the field item's \a parser
|
||||
returns 0), processing continues but the error result is returned.
|
||||
|
||||
Can be used recursively to parse deep hierarchies.
|
||||
|
||||
\param dict The dictionary object to parse
|
||||
\param fields Array of field items describing the structure. Terminated
|
||||
by a field item with a null \a name pointer.
|
||||
\param data Pointer to the structure into which the data will be
|
||||
parsed.
|
||||
\param messages Array object supplied by the caller used for storing
|
||||
messages. The messages may or may not indicate errors (its
|
||||
contents are not checked). This function itself will add
|
||||
only string objects.
|
||||
If there are any errors, suitable messages will be found in
|
||||
the \a messages object. However, just because there are no
|
||||
errors doesn't mean that \a messages will remain empty as
|
||||
a field's \a parser may add other messages. The standard
|
||||
message format is "[line number]: [message]". If the line
|
||||
number is 0, then the actual line is unknown (due to the
|
||||
source item not being parsed from a file or string).
|
||||
\return 0 if there are any errors, 1 if there are no errors.
|
||||
*/
|
||||
int PL_ParseDictionary (const plfield_t *fields, const plitem_t *dict,
|
||||
void *data, plitem_t *messages);
|
||||
|
||||
///@}
|
||||
|
||||
#endif//__QF_qfplist_h
|
||||
|
|
|
@ -38,11 +38,13 @@
|
|||
|
||||
#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.
|
||||
|
@ -97,6 +99,13 @@ typedef struct pldata_s { // Unparsed property list string
|
|||
: 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)
|
||||
|
@ -1052,3 +1061,119 @@ PL_Line (plitem_t *item)
|
|||
{
|
||||
return item->line;
|
||||
}
|
||||
|
||||
static void __attribute__((format(printf,3,4)))
|
||||
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;
|
||||
int (*parser) (const plfield_t *, const plitem_t *, void *,
|
||||
plitem_t *);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!f->name) {
|
||||
pl_message (messages, dict, "error: unknown field %s",
|
||||
current->key);
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
free (list);
|
||||
return result;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue