Add OpenStep coercion support for non-OpenStep plist types

- 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
This commit is contained in:
Nikias Bassen
2026-03-22 19:16:51 +01:00
parent 3edac28498
commit c8b36a80ba
5 changed files with 164 additions and 35 deletions
+11
View File
@@ -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
+24
View File
@@ -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.
+115 -28
View File
@@ -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,16 +539,22 @@ 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;
+2 -2
View File
@@ -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);
+12 -5
View File
@@ -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);