From 8bd5f4f201206bf64b03a757963fd8b5a5263a62 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Thu, 2 Jul 2020 14:20:46 +0900 Subject: [PATCH] [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. --- include/QF/qfplist.h | 83 ++++++++++++++++++++++++++++ libs/util/qfplist.c | 125 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) diff --git a/include/QF/qfplist.h b/include/QF/qfplist.h index a7c73ec44..5d719b840 100644 --- a/include/QF/qfplist.h +++ b/include/QF/qfplist.h @@ -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 diff --git a/libs/util/qfplist.c b/libs/util/qfplist.c index 04dd32055..35f136374 100644 --- a/libs/util/qfplist.c +++ b/libs/util/qfplist.c @@ -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; +}