summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2026-03-22 19:16:51 +0100
committerGravatar Nikias Bassen2026-03-22 19:16:51 +0100
commitc8b36a80bad4a1fe488927af4da0ecbcf10079bb (patch)
tree657066f92a85dc43bd51af7698e76d00c29c0b4a
parent3edac28498d883f1f768699ee15ce85a82bb2a7b (diff)
downloadlibplist-c8b36a80bad4a1fe488927af4da0ecbcf10079bb.tar.gz
libplist-c8b36a80bad4a1fe488927af4da0ecbcf10079bb.tar.bz2
Add OpenStep coercion support for non-OpenStep plist typesHEADmaster
- 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.111
-rw-r--r--include/plist/plist.h24
-rw-r--r--src/oplist.c143
-rw-r--r--src/plist.c4
-rw-r--r--tools/plistutil.c17
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);