[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:
Bill Currie 2020-07-02 14:20:46 +09:00
parent f6ea9e4d87
commit 8bd5f4f201
2 changed files with 208 additions and 0 deletions

View File

@ -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

View File

@ -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;
}