summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2026-02-20 03:38:36 +0100
committerGravatar Nikias Bassen2026-02-20 03:38:36 +0100
commite3d5bbdf1f213caafedf185251fba02558d30b98 (patch)
treedc62bee53ebb9b3057b2a6fef043684efcefc125
parent30132a9e2ce44f8325d574ea7a3c28466c132f0e (diff)
downloadlibplist-e3d5bbdf1f213caafedf185251fba02558d30b98.tar.gz
libplist-e3d5bbdf1f213caafedf185251fba02558d30b98.tar.bz2
plistutil: Add a --nodepath option to allow selecting a specific node
-rw-r--r--docs/plistutil.146
-rw-r--r--tools/plistutil.c70
2 files changed, 114 insertions, 2 deletions
diff --git a/docs/plistutil.1 b/docs/plistutil.1
index b1f7773..f322bab 100644
--- a/docs/plistutil.1
+++ b/docs/plistutil.1
@@ -29,6 +29,52 @@ convert to/from JSON or OpenStep the output format needs to specified.
29.B \-p, \-\-print FILE 29.B \-p, \-\-print FILE
30Print PList in human-readable format. 30Print PList in human-readable format.
31.TP 31.TP
32.B \-n, \-\-nodepath PATH
33Restrict output to nodepath defined by
34.I PATH
35which selects a specific node within the plist document.
36A node path describes traversal through dictionaries and arrays using / to
37separate the components.
38Traversal begins at the root node and proceeds from left to right.
39Each component selects a child of the current node.
40If the current node is a dictionary, the component is interpreted as a
41dictionary key name.
42If the current node is an array, the component must be a non-negative decimal
43integer specifying the array index (0-based).
44
45Given the following structure:
46.PP
47.RS
48.nf
49<plist version="1.0">
50<dict>
51 <key>Users</key>
52 <array>
53 <dict>
54 <key>Name</key>
55 <string>Alice</string>
56 </dict>
57 <dict>
58 <key>Name</key>
59 <string>Bob</string>
60 </dict>
61 </array>
62</dict>
63</plist>
64.fi
65
66A nodepath of:
67.TP
68\f[B]Users\f[] resolves to the entire array.
69.TP
70\f[B]Users/0\f[] resolves to the first dictionary in the array.
71.TP
72\f[B]Users/0/Name\f[] resolves to the string value "Alice".
73.TP
74\f[B]Users/1/Name\f[] resolves to the string value "Bob".
75.RE
76
77.TP
32.B \-c, \-\-compact 78.B \-c, \-\-compact
33JSON and OpenStep only: Print output in compact form. By default, the output 79JSON and OpenStep only: Print output in compact form. By default, the output
34will be pretty-printed. 80will be pretty-printed.
diff --git a/tools/plistutil.c b/tools/plistutil.c
index 84e7add..6e697cf 100644
--- a/tools/plistutil.c
+++ b/tools/plistutil.c
@@ -40,11 +40,12 @@
40#ifdef _MSC_VER 40#ifdef _MSC_VER
41#pragma warning(disable:4996) 41#pragma warning(disable:4996)
42#define STDIN_FILENO _fileno(stdin) 42#define STDIN_FILENO _fileno(stdin)
43#define strtok_r strtok_s
43#endif 44#endif
44 45
45typedef struct _options 46typedef struct _options
46{ 47{
47 char *in_file, *out_file; 48 char *in_file, *out_file, *nodepath;
48 uint8_t in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json, 4 = openstep 49 uint8_t in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json, 4 = openstep
49 uint8_t flags; 50 uint8_t flags;
50} options_t; 51} options_t;
@@ -70,6 +71,7 @@ static void print_usage(int argc, char *argv[])
70 printf(" If omitted, XML will be converted to binary,\n"); 71 printf(" If omitted, XML will be converted to binary,\n");
71 printf(" and binary to XML.\n"); 72 printf(" and binary to XML.\n");
72 printf(" -p, --print FILE Print the PList in human-readable format.\n"); 73 printf(" -p, --print FILE Print the PList in human-readable format.\n");
74 printf(" -n, --nodepath PATH Restrict output to nodepath defined by PATH.\n");
73 printf(" -c, --compact JSON and OpenStep only: Print output in compact form.\n"); 75 printf(" -c, --compact JSON and OpenStep only: Print output in compact form.\n");
74 printf(" By default, the output will be pretty-printed.\n"); 76 printf(" By default, the output will be pretty-printed.\n");
75 printf(" -s, --sort Sort all dictionary nodes lexicographically by key\n"); 77 printf(" -s, --sort Sort all dictionary nodes lexicographically by key\n");
@@ -96,6 +98,7 @@ static options_t *parse_arguments(int argc, char *argv[])
96 { "compact", no_argument, 0, 'c' }, 98 { "compact", no_argument, 0, 'c' },
97 { "sort", no_argument, 0, 's' }, 99 { "sort", no_argument, 0, 's' },
98 { "print", required_argument, 0, 'p' }, 100 { "print", required_argument, 0, 'p' },
101 { "nodepath", required_argument, 0, 'n' },
99 { "debug", no_argument, 0, 'd' }, 102 { "debug", no_argument, 0, 'd' },
100 { "help", no_argument, 0, 'h' }, 103 { "help", no_argument, 0, 'h' },
101 { "version", no_argument, 0, 'v' }, 104 { "version", no_argument, 0, 'v' },
@@ -103,7 +106,7 @@ static options_t *parse_arguments(int argc, char *argv[])
103 }; 106 };
104 107
105 int c; 108 int c;
106 while ((c = getopt_long(argc, argv, "i:o:f:csp:dhv", long_options, NULL)) != -1) 109 while ((c = getopt_long(argc, argv, "i:o:f:csp:n:dhv", long_options, NULL)) != -1)
107 { 110 {
108 switch (c) 111 switch (c)
109 { 112 {
@@ -175,6 +178,15 @@ static options_t *parse_arguments(int argc, char *argv[])
175 break; 178 break;
176 } 179 }
177 180
181 case 'n':
182 if (!optarg || optarg[0] == '\0') {
183 fprintf(stderr, "ERROR: --extract needs a node path\n");
184 free(options);
185 return NULL;
186 }
187 options->nodepath = optarg;
188 break;
189
178 case 'd': 190 case 'd':
179 options->flags |= OPT_DEBUG; 191 options->flags |= OPT_DEBUG;
180 break; 192 break;
@@ -326,6 +338,60 @@ int main(int argc, char *argv[])
326 { 338 {
327 input_res = plist_from_memory(plist_entire, read_size, &root_node, NULL); 339 input_res = plist_from_memory(plist_entire, read_size, &root_node, NULL);
328 if (input_res == PLIST_ERR_SUCCESS) { 340 if (input_res == PLIST_ERR_SUCCESS) {
341
342 if (options->nodepath) {
343 char *copy = strdup(options->nodepath);
344 char *tok, *saveptr = NULL;
345 if (!copy) {
346 plist_free(root_node);
347 free(plist_entire);
348 free(options);
349 return 1;
350 }
351
352 plist_t current = root_node;
353 for (tok = strtok_r(copy, "/", &saveptr); tok; tok = strtok_r(NULL, "/", &saveptr)) {
354 if (*tok == '\0') continue;
355 switch (plist_get_node_type(current)) {
356 case PLIST_DICT:
357 current = plist_dict_get_item(current, tok);
358 break;
359 case PLIST_ARRAY: {
360 char* endp = NULL;
361 uint32_t idx = strtoul(tok, &endp, 10);
362 if (endp == tok || *endp != '\0') {
363 current = NULL;
364 break;
365 }
366 if (idx >= plist_array_get_size(current)) {
367 current = NULL;
368 break;
369 }
370 current = plist_array_get_item(current, idx);
371 break;
372 }
373 default:
374 current = NULL;
375 break;
376 }
377 if (!current) {
378 break;
379 }
380 }
381 free(copy);
382 if (current) {
383 plist_t destnode = plist_copy(current);
384 plist_free(root_node);
385 root_node = destnode;
386 } else {
387 fprintf(stderr, "ERROR: nodepath '%s' is invalid\n", options->nodepath);
388 plist_free(root_node);
389 free(plist_entire);
390 free(options);
391 return 1;
392 }
393 }
394
329 if (options->flags & OPT_SORT) { 395 if (options->flags & OPT_SORT) {
330 plist_sort(root_node); 396 plist_sort(root_node);
331 } 397 }