diff options
| author | 2026-03-22 19:16:51 +0100 | |
|---|---|---|
| committer | 2026-03-22 19:16:51 +0100 | |
| commit | c8b36a80bad4a1fe488927af4da0ecbcf10079bb (patch) | |
| tree | 657066f92a85dc43bd51af7698e76d00c29c0b4a | |
| parent | 3edac28498d883f1f768699ee15ce85a82bb2a7b (diff) | |
| download | libplist-c8b36a80bad4a1fe488927af4da0ecbcf10079bb.tar.gz libplist-c8b36a80bad4a1fe488927af4da0ecbcf10079bb.tar.bz2 | |
- Use PLIST_OPT_COERCE option to coerce PLIST_BOOLEAN, PLIST_DATE, PLIST_UID, and PLIST_NULL to OpenStep-compatible types (1 or 0, ISO 8601 strings, integers, and 'NULL' string)
- Add plist_to_openstep_with_options() function to allow passing coercion option (and others)
- Update plist_write_to_string() and plist_write_to_stream() accordingly
| -rw-r--r-- | docs/plistutil.1 | 11 | ||||
| -rw-r--r-- | include/plist/plist.h | 24 | ||||
| -rw-r--r-- | src/oplist.c | 143 | ||||
| -rw-r--r-- | src/plist.c | 4 | ||||
| -rw-r--r-- | tools/plistutil.c | 17 |
5 files changed, 164 insertions, 35 deletions
diff --git a/docs/plistutil.1 b/docs/plistutil.1 index f322bab..64373b3 100644 --- a/docs/plistutil.1 +++ b/docs/plistutil.1 @@ -79,6 +79,17 @@ A nodepath of: JSON and OpenStep only: Print output in compact form. By default, the output will be pretty-printed. .TP +.B \-C, \-\-coerce +JSON and OpenStep only: Coerce non-compatible plist types to JSON/OpenStep +compatible representations. +Date values become ISO 8601 strings, +data values become Base64-encoded strings (JSON), +UID values become integers, +boolean becomes 1 or 0 (OpenStep), +and NULL becomes a string 'NULL' (OpenStep) + +This options is implied when invoked as plist2json. +.TP .B \-s, \-\-sort Sort all dictionary nodes lexicographically by key before converting to the output format. .TP diff --git a/include/plist/plist.h b/include/plist/plist.h index bd35c53..94930b8 100644 --- a/include/plist/plist.h +++ b/include/plist/plist.h @@ -978,6 +978,30 @@ extern "C" */ PLIST_API plist_err_t plist_to_openstep(plist_t plist, char **plist_openstep, uint32_t* length, int prettify); + /** + * Export the #plist_t structure to OpenStep format with extended options. + * + * When \a PLIST_OPT_COMPACT is set in \a options, the resulting OpenStep output + * will be non-prettified. + * + * When \a PLIST_OPT_COERCE is set in \a options, plist types that have + * no native OpenStep representation are converted to OpenStep-compatible types + * instead of returning #PLIST_ERR_FORMAT: + * - #PLIST_BOOLEAN is serialized as a 1 or 0 + * - #PLIST_DATE is serialized as an ISO 8601 date string + * - #PLIST_NULL is serialized as string 'NULL' + * - #PLIST_UID is serialized as an integer + * + * @param plist the root node to export + * @param plist_openstep a pointer to a char* buffer. This function allocates the memory, + * caller is responsible for freeing it. + * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer. + * @param options One or more bitwise ORed values of #plist_write_options_t. + * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure + * @note Use plist_mem_free() to free the allocated memory. + */ + PLIST_API plist_err_t plist_to_openstep_with_options(plist_t plist, char **plist_openstep, uint32_t* length, plist_write_options_t options); + /** * Import the #plist_t structure from XML format. diff --git a/src/oplist.c b/src/oplist.c index 0eea27a..3c48b3a 100644 --- a/src/oplist.c +++ b/src/oplist.c @@ -37,8 +37,11 @@ #include "plist.h" #include "strbuf.h" +#include "time64.h" #include "hashtable.h" +#define MAC_EPOCH 978307200 + #ifdef DEBUG static int plist_ostep_debug = 0; #define PLIST_OSTEP_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepparser] ERROR: " __VA_ARGS__); } @@ -144,7 +147,7 @@ static int str_needs_quotes(const char* str, size_t len) return 0; } -static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify) +static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify, int coerce) { plist_data_t node_data = NULL; @@ -235,7 +238,7 @@ static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t str_buf_append(*outbuf, " ", 2); } } - plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify); + plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify, coerce); if (res < 0) { return res; } @@ -263,7 +266,7 @@ static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t str_buf_append(*outbuf, " ", 2); } } - plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify); + plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify, coerce); if (res < 0) { return res; } @@ -302,19 +305,65 @@ static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t str_buf_append(*outbuf, ">", 1); } break; case PLIST_BOOLEAN: - PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n"); - return PLIST_ERR_FORMAT; + if (coerce) { + if (node_data->boolval) { + str_buf_append(*outbuf, "1", 1); + } else { + str_buf_append(*outbuf, "0", 1); + } + } else { + PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + } + break; case PLIST_NULL: - PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n"); - return PLIST_ERR_FORMAT; + if (coerce) { + str_buf_append(*outbuf, "NULL", 4); + } else { + PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + } + break; case PLIST_DATE: - // NOT VALID FOR OPENSTEP - PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n"); - return PLIST_ERR_FORMAT; + if (coerce) { + Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH; + struct TM _btime; + struct TM *btime = gmtime64_r(&timev, &_btime); + char datebuf[32]; + size_t datelen = 0; + if (btime) { + struct tm _tmcopy; + copy_TM64_to_tm(btime, &_tmcopy); + datelen = strftime(datebuf, sizeof(datebuf), "%Y-%m-%dT%H:%M:%SZ", &_tmcopy); + } + if (datelen <= 0) { + datelen = snprintf(datebuf, sizeof(datebuf), "1970-01-01T00:00:00Z"); + } + str_buf_append(*outbuf, "\"", 1); + str_buf_append(*outbuf, datebuf, datelen); + str_buf_append(*outbuf, "\"", 1); + } else { + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + } + break; case PLIST_UID: - // NOT VALID FOR OPENSTEP - PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n"); - return PLIST_ERR_FORMAT; + if (coerce) { + val = (char*)malloc(64); + if (node_data->length == 16) { + val_len = snprintf(val, 64, "%" PRIu64, node_data->intval); + } else { + val_len = snprintf(val, 64, "%" PRIi64, node_data->intval); + } + str_buf_append(*outbuf, val, val_len); + free(val); + } else { + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + } + break; default: return PLIST_ERR_UNKNOWN; } @@ -360,7 +409,7 @@ static int num_digits_u(uint64_t i) return n; } -static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, hashtable_t *visited) +static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, int coerce, hashtable_t *visited) { plist_data_t data; if (!node) { @@ -385,7 +434,7 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep node_t ch; unsigned int n_children = node_n_children(node); for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) { - plist_err_t res = _node_estimate_size(ch, size, depth + 1, prettify, visited); + plist_err_t res = _node_estimate_size(ch, size, depth + 1, prettify, coerce, visited); if (res < 0) { return res; } @@ -442,17 +491,46 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep *size += data->length/4; break; case PLIST_BOOLEAN: - // NOT VALID FOR OPENSTEP - PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n"); - return PLIST_ERR_FORMAT; + if (coerce) { + *size += 1; + } else { + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + } + break; case PLIST_DATE: - // NOT VALID FOR OPENSTEP - PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n"); - return PLIST_ERR_FORMAT; + if (coerce) { + // ISO 8601 string: "YYYY-MM-DDTHH:MM:SSZ" = 22 chars max + *size += 24; + } else { + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + } + break; case PLIST_UID: - // NOT VALID FOR OPENSTEP - PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n"); - return PLIST_ERR_FORMAT; + if (coerce) { + if (data->length == 16) { + *size += num_digits_u(data->intval); + } else { + *size += num_digits_i((int64_t)data->intval); + } + } else { + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + } + break; + case PLIST_NULL: + if (coerce) { + *size += 4; + } else { + // NOT VALID FOR OPENSTEP + PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n"); + return PLIST_ERR_FORMAT; + } + break; default: PLIST_OSTEP_WRITE_ERR("invalid node type encountered\n"); return PLIST_ERR_UNKNOWN; @@ -461,17 +539,23 @@ static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t dep return PLIST_ERR_SUCCESS; } -static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify) +static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, int coerce) { hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL); if (!visited) return PLIST_ERR_NO_MEM; - plist_err_t err = _node_estimate_size(node, size, depth, prettify, visited); + plist_err_t err = _node_estimate_size(node, size, depth, prettify, coerce, visited); hash_table_destroy(visited); return err; } plist_err_t plist_to_openstep(plist_t plist, char **openstep, uint32_t* length, int prettify) { + plist_write_options_t opts = prettify ? PLIST_OPT_NONE : PLIST_OPT_COMPACT; + return plist_to_openstep_with_options(plist, openstep, length, opts); +} + +plist_err_t plist_to_openstep_with_options(plist_t plist, char **openstep, uint32_t* length, plist_write_options_t options) +{ uint64_t size = 0; plist_err_t res; @@ -479,7 +563,10 @@ plist_err_t plist_to_openstep(plist_t plist, char **openstep, uint32_t* length, return PLIST_ERR_INVALID_ARG; } - res = node_estimate_size((node_t)plist, &size, 0, prettify); + int prettify = !(options & PLIST_OPT_COMPACT); + int coerce = options & PLIST_OPT_COERCE; + + res = node_estimate_size((node_t)plist, &size, 0, prettify, coerce); if (res < 0) { return res; } @@ -490,7 +577,7 @@ plist_err_t plist_to_openstep(plist_t plist, char **openstep, uint32_t* length, return PLIST_ERR_NO_MEM; } - res = node_to_openstep((node_t)plist, &outbuf, 0, prettify); + res = node_to_openstep((node_t)plist, &outbuf, 0, prettify, coerce); if (res < 0) { str_buf_free(outbuf); *openstep = NULL; diff --git a/src/plist.c b/src/plist.c index a6d3547..2ad1b0a 100644 --- a/src/plist.c +++ b/src/plist.c @@ -2407,7 +2407,7 @@ plist_err_t plist_write_to_string(plist_t plist, char **output, uint32_t* length err = plist_to_json_with_options(plist, output, length, options); break; case PLIST_FORMAT_OSTEP: - err = plist_to_openstep(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0)); + err = plist_to_openstep_with_options(plist, output, length, options); break; case PLIST_FORMAT_PRINT: err = plist_write_to_string_default(plist, output, length, options); @@ -2445,7 +2445,7 @@ plist_err_t plist_write_to_stream(plist_t plist, FILE *stream, plist_format_t fo err = plist_to_json_with_options(plist, &output, &length, options); break; case PLIST_FORMAT_OSTEP: - err = plist_to_openstep(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0)); + err = plist_to_openstep_with_options(plist, &output, &length, options); break; case PLIST_FORMAT_PRINT: err = plist_write_to_stream_default(plist, stream, options); diff --git a/tools/plistutil.c b/tools/plistutil.c index fef72b7..95ec254 100644 --- a/tools/plistutil.c +++ b/tools/plistutil.c @@ -75,10 +75,14 @@ static void print_usage(int argc, char *argv[]) printf(" -n, --nodepath PATH Restrict output to nodepath defined by PATH.\n"); printf(" -c, --compact JSON and OpenStep only: Print output in compact form.\n"); printf(" By default, the output will be pretty-printed.\n"); - printf(" -C, --coerce JSON only: Coerce non-JSON plist types to JSON-compatible\n"); - printf(" representations. Date values become ISO 8601 strings,\n"); - printf(" data values become Base64-encoded strings, and UID values\n"); - printf(" become integers. Implied when invoked as plist2json.\n"); + printf(" -C, --coerce JSON + OpenStep only: Coerce non-compatible plist types\n"); + printf(" to JSON/OpenStep compatible representations.\n"); + printf(" Date values become ISO 8601 strings,\n"); + printf(" data values become Base64-encoded strings (JSON),\n"); + printf(" UID values become integers,\n"); + printf(" boolean becomes 1 or 0 (OpenStep),\n"); + printf(" and NULL becomes a string 'NULL' (OpenStep)\n"); + printf(" This options is implied when invoked as plist2json.\n"); printf(" -s, --sort Sort all dictionary nodes lexicographically by key\n"); printf(" before converting to the output format.\n"); printf(" -d, --debug Enable extended debug output\n"); @@ -431,7 +435,10 @@ int main(int argc, char *argv[]) if (options->flags & OPT_COERCE) wropts |= PLIST_OPT_COERCE; output_res = plist_to_json_with_options(root_node, &plist_out, &size, wropts); } else if (options->out_fmt == PLIST_FORMAT_OSTEP) { - output_res = plist_to_openstep(root_node, &plist_out, &size, !(options->flags & OPT_COMPACT)); + plist_write_options_t wropts = PLIST_OPT_NONE; + if (options->flags & OPT_COMPACT) wropts |= PLIST_OPT_COMPACT; + if (options->flags & OPT_COERCE) wropts |= PLIST_OPT_COERCE; + output_res = plist_to_openstep_with_options(root_node, &plist_out, &size, wropts); } else { plist_write_to_stream(root_node, stdout, options->out_fmt, PLIST_OPT_PARTIAL_DATA); plist_free(root_node); |
