GRASS 8 Programmer's Manual 8.6.0dev(2026)-ddeab64dbf
Loading...
Searching...
No Matches
parser_json.c
Go to the documentation of this file.
1/*!
2 \file lib/gis/parser_json.c
3
4 \brief GIS Library - converts the command line arguments into actinia JSON
5 process chain building blocks
6
7 (C) 2018-2021 by the GRASS Development Team
8
9 This program is free software under the GNU General Public License
10 (>=v2). Read the file COPYING that comes with GRASS for details.
11
12 \author Soeren Gebbert
13 */
14
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <grass/glocale.h>
19#include <grass/gis.h>
20
21#include "parser_local_proto.h"
22
23void check_create_import_opts(struct Option *, char *, FILE *);
24void check_create_export_opts(struct Option *, char *, FILE *);
25char *check_mapset_in_layer_name(char *, int);
26static char *str_json_escape(const char *str);
27static char *str_replace_free_buffer(char *buffer, const char old_char,
28 const char *new_str);
29
30/*!
31 \brief This function generates actinia JSON process chain building blocks
32 from the command line arguments that can be used in the actinia processing
33 API.
34
35 The following commands will create according JSON output:
36
37 r.slope.aspect
38 elevation="elevation@https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif"
39 slope="slope+GTiff" aspect="aspect+GTiff" --json
40
41 {
42 "module": "r.slope.aspect",
43 "id": "r.slope.aspect_1804289383",
44 "inputs":[
45 {"import_descr":
46 {"source":"https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif",
47 "type":"raster"}, "param": "elevation", "value": "elevation"},
48 {"param": "format", "value": "degrees"},
49 {"param": "precision", "value": "FCELL"},
50 {"param": "zscale", "value": "1.0"},
51 {"param": "min_slope", "value": "0.0"}
52 ],
53 "outputs":[
54 {"export": {"format":"GTiff", "type":"raster"},
55 "param": "slope", "value": "slope"},
56 {"export": {"format":"GTiff", "type":"raster"},
57 "param": "aspect", "value": "aspect"}
58 ]
59 }
60
61 v.out.ascii input="hospitals@PERMANENT" output="myfile+TXT" --json
62
63 {
64 "module": "v.out.ascii",
65 "id": "v.out.ascii_1804289383",
66 "inputs":[
67 {"param": "input", "value": "hospitals@PERMANENT"},
68 {"param": "layer", "value": "1"},
69 {"param": "type", "value": "point,line,boundary,centroid,area,face,kernel"},
70 {"param": "format", "value": "point"},
71 {"param": "separator", "value": "pipe"},
72 {"param": "precision", "value": "8"}
73 ],
74 "outputs":[
75 {"export": {"format":"TXT", "type":"file"},
76 "param": "output", "value": "$file::myfile"}
77 ]
78 }
79
80 v.info map="hospitals@PERMANENT" -c --json
81
82 {
83 "module": "v.info",
84 "id": "v.info_1804289383",
85 "flags":"c",
86 "inputs":[
87 {"param": "map", "value": "hospitals@PERMANENT"},
88 {"param": "layer", "value": "1"}
89 ]
90 }
91
92
93 A process chain has the following form
94
95 {
96 'list': [{
97 'module': 'g.region',
98 'id': 'g_region_1',
99 'inputs': [{'import_descr': {'source':
100 'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif', 'type':
101 'raster'}, 'param': 'raster', 'value': 'elev_ned_30m_new'}], 'flags': 'p'
102 },
103 {
104 'module': 'r.slope.aspect',
105 'id': 'r_slope_aspect_1',
106 'inputs': [{'param': 'elevation',
107 'value': 'elev_ned_30m_new'}],
108 'outputs': [{'export': {'format': 'GTiff',
109 'type': 'raster'},
110 'param': 'slope',
111 'value': 'elev_ned_30m_new_slope'}],
112 'flags': 'a'},
113 {
114 'module': 'r.univar',
115 'id': 'r_univar_1',
116 'inputs': [{"import_descr": {"source": "LT52170762005240COA00",
117 "type": "landsat",
118 "landsat_atcor": "dos1"},
119 'param': 'map',
120 'value': 'LT52170762005240COA00_dos1.1'}],
121 'stdout': {'id': 'stats', 'format': 'kv', 'delimiter': '='},
122 'flags': 'a'
123 },
124 {
125 'module': 'exporter',
126 'id': 'exporter_1',
127 'outputs': [{'export': {'format': 'GTiff',
128 'type': 'raster'},
129 'param': 'map',
130 'value': 'LT52170762005240COA00_dos1.1'}]
131 },
132 {
133 "id": "ascii_out",
134 "module": "r.out.ascii",
135 "inputs": [{"param": "input",
136 "value": "elevation@PERMANENT"},
137 {"param": "precision", "value": "0"}],
138 "stdout": {"id": "elev_1", "format": "table", "delimiter": " "},
139 "flags": "h"
140 },
141 {
142 "id": "ascii_export",
143 "module": "r.out.ascii",
144 "inputs": [{"param": "input",
145 "value": "elevation@PERMANENT"}],
146 "outputs": [
147 {"export": {"type": "file", "format": "TXT"},
148 "param": "output",
149 "value": "$file::out1"}
150 ]
151 },
152 {
153 "id": "raster_list",
154 "module": "g.list",
155 "inputs": [{"param": "type",
156 "value": "raster"}],
157 "stdout": {"id": "raster", "format": "list", "delimiter": "\n"}
158 },
159 {
160 "module": "r.what",
161 "id": "r_what_1",
162 "verbose": True,
163 "flags": "nfic",
164 "inputs": [
165 {
166 "param": "map",
167 "value": "landuse96_28m@PERMANENT"
168 },
169 {
170 "param": "coordinates",
171 "value": "633614.08,224125.12,632972.36,225382.87"
172 },
173 {
174 "param": "null_value",
175 "value": "null"
176 },
177 {
178 "param": "separator",
179 "value": "pipe"
180 }
181 ],
182 "stdout": {"id": "sample", "format": "table", "delimiter": "|"}
183 }
184 ],
185 'webhooks': {'update':
186 'http://business-logic.company.com/api/v1/actinia-update-webhook',
187 'finished':
188 'http://business-logic.company.com/api/v1/actinia-finished-webhook'},
189 'version': '1'
190 }
191
192 */
193char *G__json(void)
194{
195 FILE *fp = stdout;
196
197 /*FILE *fp = NULL; */
198 char *file_name = NULL;
199 int c;
200 int random_int = rand();
201 int num_flags = 0;
202 int num_inputs = 0;
203 int num_outputs = 0;
204 int i = 0;
205
206 char age[KEYLENGTH];
207 char element[KEYLENGTH]; /*cell, file, grid3, vector */
208 char desc[KEYLENGTH];
209
210 file_name = G_tempfile();
211
212 /* fprintf(stderr, "Filename: %s\n", file_name); */
213 fp = fopen(file_name, "w+");
214 if (fp == NULL) {
215 fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
217 }
218
219 if (st->n_flags) {
220 struct Flag *flag;
221
222 for (flag = &st->first_flag; flag; flag = flag->next_flag) {
223 if (flag->answer)
224 num_flags += 1;
225 ;
226 }
227 }
228
229 /* Count input and output options */
230 if (st->n_opts) {
231 struct Option *opt;
232
233 for (opt = &st->first_option; opt; opt = opt->next_opt) {
234 if (opt->answer) {
235 if (opt->gisprompt) {
236 G__split_gisprompt(opt->gisprompt, age, element, desc);
237 /* fprintf(stderr, "age: %s element: %s desc: %s\n", age,
238 * element, desc); */
239 if (G_strncasecmp("new", age, 3) == 0) {
240 /*fprintf(fp, "new: %s\n", opt->gisprompt); */
241 num_outputs += 1;
242 }
243 else {
244 /*fprintf(fp, "%s\n", opt->gisprompt); */
245 num_inputs += 1;
246 }
247 }
248 else {
249 num_inputs += 1;
250 }
251 }
252 }
253 }
254
255 fprintf(fp, "{\n");
256 fprintf(fp, " \"module\": \"%s\",\n", G_program_name());
257 fprintf(fp, " \"id\": \"%s_%i\"", G_program_name(), random_int);
258
259 if (st->n_flags && num_flags > 0) {
260 struct Flag *flag;
261
262 fprintf(fp, ",\n");
263 fprintf(fp, " \"flags\":\"");
264
265 for (flag = &st->first_flag; flag; flag = flag->next_flag) {
266 if (flag->answer)
267 fprintf(fp, "%c", flag->key);
268 }
269 fprintf(fp, "\"");
270 }
271
272 /* Print the input options
273 */
274 if (st->n_opts && num_inputs > 0) {
275 struct Option *opt;
276
277 i = 0;
278 fprintf(fp, ",\n");
279 fprintf(fp, " \"inputs\":[\n");
280 for (opt = &st->first_option; opt; opt = opt->next_opt) {
281 if (opt->gisprompt) {
282 G__split_gisprompt(opt->gisprompt, age, element, desc);
283 if (G_strncasecmp("new", age, 3) != 0) {
284 if (opt->answer) {
286 i++;
287 if (i < num_inputs) {
288 fprintf(fp, ",\n");
289 }
290 else {
291 fprintf(fp, "\n");
292 }
293 }
294 }
295 }
296 else if (opt->answer) {
297 /* Check for input options */
298 fprintf(fp, " {\"param\": \"%s\", ", opt->key);
299 char *escaped_value = str_json_escape(opt->answer);
300 fprintf(fp, "\"value\": \"%s\"}", escaped_value);
302 i++;
303 if (i < num_inputs) {
304 fprintf(fp, ",\n");
305 }
306 else {
307 fprintf(fp, "\n");
308 }
309 }
310 }
311 fprintf(fp, " ]");
312 }
313
314 /* Print the output options
315 */
316 if (st->n_opts && num_outputs > 0) {
317 struct Option *opt;
318
319 i = 0;
320 fprintf(fp, ",\n");
321 fprintf(fp, " \"outputs\":[\n");
322 for (opt = &st->first_option; opt; opt = opt->next_opt) {
323 if (opt->gisprompt) {
324 G__split_gisprompt(opt->gisprompt, age, element, desc);
325 if (G_strncasecmp("new", age, 3) == 0) {
326 if (opt->answer) {
328 i++;
329 if (i < num_outputs) {
330 fprintf(fp, ",\n");
331 }
332 else {
333 fprintf(fp, "\n");
334 }
335 }
336 }
337 }
338 }
339 fprintf(fp, " ]\n");
340 }
341
342 fprintf(fp, "}\n");
343 fclose(fp);
344
345 /* Print the file content to stdout */
346 fp = fopen(file_name, "r");
347 if (fp == NULL) {
348 fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
350 }
351
352 c = fgetc(fp);
353 while (c != EOF) {
354 fprintf(stdout, "%c", c);
355 c = fgetc(fp);
356 }
357 fclose(fp);
358
359 return file_name;
360}
361
362/* \brief Check the provided answer and generate the import statement
363 dependent on the element type (cell, vector, grid3, file)
364
365 {'import_descr': {'source':
366 'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif', 'type':
367 'raster'}, 'param': 'map', 'value': 'elevation'}
368 */
370{
371 int i = 0, urlfound = 0;
372 int has_import = 0;
373 char **tokens;
374
375 G_debug(2, "tokenize opt string: <%s> with '@'", opt->answer);
376 tokens = G_tokenize(opt->answer, "@");
377 while (tokens[i]) {
378 G_chop(tokens[i]);
379 i++;
380 }
381 if (i > 2)
383 _("Input string not understood: <%s>. Multiple '@' chars?"),
384 opt->answer);
385
386 if (i > 1) {
387 /* check if tokens[1] starts with an URL or name@mapset */
388 G_debug(2, "tokens[1]: <%s>", tokens[1]);
389 if (strncmp(tokens[1], "http://", 7) == 0 ||
390 strncmp(tokens[1], "https://", 8) == 0 ||
391 strncmp(tokens[1], "ftp://", 6) == 0) {
392 urlfound = 1;
393 G_debug(2, "URL found");
394 }
395 else {
396 urlfound = 0;
397 G_debug(2, "name@mapset found");
398 }
399 }
400
401 fprintf(fp, " {");
402
403 if (i > 1 && urlfound == 1) {
404 if (G_strncasecmp("cell", element, 4) == 0) {
405 fprintf(fp,
406 "\"import_descr\": {\"source\":\"%s\", "
407 "\"type\":\"raster\"},\n ",
408 tokens[1]);
409 has_import = 1;
410 }
411 else if (G_strncasecmp("file", element, 4) == 0) {
412 fprintf(fp,
413 "\"import_descr\": {\"source\":\"%s\", "
414 "\"type\":\"file\"},\n ",
415 tokens[1]);
416 has_import = 1;
417 }
418 else if (G_strncasecmp("vector", element, 4) == 0) {
419 fprintf(fp,
420 "\"import_descr\": {\"source\":\"%s\", "
421 "\"type\":\"vector\"},\n ",
422 tokens[1]);
423 has_import = 1;
424 }
425 }
426
427 fprintf(fp, "\"param\": \"%s\", ", opt->key);
428 /* In case of import the mapset must be removed always */
429 if (urlfound == 1) {
430 char *escaped_value =
431 str_json_escape(check_mapset_in_layer_name(tokens[0], has_import));
432 fprintf(fp, "\"value\": \"%s\"", escaped_value);
434 }
435 else {
436 char *escaped_value = str_json_escape(
438 fprintf(fp, "\"value\": \"%s\"", escaped_value);
440 };
441 fprintf(fp, "}");
442
444}
445
446/* \brief Check the provided answer and generate the export statement
447 dependent on the element type (cell, vector, grid3, file)
448
449 "outputs": [
450 {"export": {"type": "file", "format": "TXT"},
451 "param": "output",
452 "value": "$file::out1"},
453 {'export': {'format': 'GTiff', 'type': 'raster'},
454 'param': 'map',
455 'value': 'LT52170762005240COA00_dos1.1'}
456 ]
457 */
459{
460 int i = 0;
461 int has_file_export = 0;
462 char **tokens;
463
464 tokens = G_tokenize(opt->answer, "+");
465 while (tokens[i]) {
466 G_chop(tokens[i]);
467 i++;
468 }
469
470 fprintf(fp, " {");
471
472 if (i > 1) {
473 if (G_strncasecmp("cell", element, 4) == 0) {
474 fprintf(
475 fp,
476 "\"export\": {\"format\":\"%s\", \"type\":\"raster\"},\n ",
477 tokens[1]);
478 }
479 else if (G_strncasecmp("file", element, 4) == 0) {
480 fprintf(
481 fp,
482 "\"export\": {\"format\":\"%s\", \"type\":\"file\"},\n ",
483 tokens[1]);
484 has_file_export = 1;
485 }
486 else if (G_strncasecmp("vector", element, 4) == 0) {
487 fprintf(
488 fp,
489 "\"export\": {\"format\":\"%s\", \"type\":\"vector\"},\n ",
490 tokens[1]);
491 }
492 }
493
494 fprintf(fp, "\"param\": \"%s\", ", opt->key);
495 if (has_file_export == 1) {
496 char *escaped_value =
497 str_json_escape(check_mapset_in_layer_name(tokens[0], 1));
498 fprintf(fp, "\"value\": \"$file::%s\"", escaped_value);
500 }
501 else {
502 char *escaped_value =
503 str_json_escape(check_mapset_in_layer_name(tokens[0], 1));
504 fprintf(fp, "\"value\": \"%s\"", escaped_value);
506 }
507 fprintf(fp, "}");
508
510}
511
512/*
513 \brief Check if the current mapset is present in the layer name and remove it
514
515 The flag always_remove tells this function to always remove all mapset names.
516
517 \return pointer to the layer name without the current mapset
518 */
519char *check_mapset_in_layer_name(char *layer_name, int always_remove)
520{
521 int i = 0;
522 char **tokens;
523 const char *mapset;
524
525 mapset = G_mapset();
526
527 tokens = G_tokenize(layer_name, "@");
528
529 while (tokens[i]) {
530 G_chop(tokens[i]);
531 /* fprintf(stderr, "Token %i: %s\n", i, tokens[i]); */
532 i++;
533 }
534
535 if (always_remove == 1)
536 return tokens[0];
537
538 if (i > 1 && G_strcasecmp(mapset, tokens[1]) == 0)
539 return tokens[0];
540
541 return layer_name;
542}
543
544/** \brief Replace *old_char* with *new_str* and free the *buffer* if needed
545 *
546 * Replaces *old_char* (one character) with *new_str* (a string) in *buffer*
547 * and returns the new string. If no replacements were made, returns pointer
548 * to the original *buffer* unchanged.
549 *
550 * In any case, returned string should be freed with G_free(), while
551 * the original *buffer* should not be freed. The *buffer* must be a heap
552 * allocated string.
553 *
554 * \param buffer the string to make replacements in
555 * \param old_char the character to replace
556 * \param new_str the replacement string
557 *
558 * \return a newly allocated string or *buffer* if no replacements were made
559 */
560static char *str_replace_free_buffer(char *buffer, const char old_char,
561 const char *new_str)
562{
563 // Check if the input string contains the character to replace to avoid
564 // allocation in G_str_replace.
565 if (strchr(buffer, old_char)) {
566 // The function takes strings, not chars.
567 char old_str[2] = {old_char, '\0'};
568 char *res = G_str_replace(buffer, old_str, new_str);
569 G_free(buffer);
570 return res;
571 }
572 return buffer;
573}
574
575/** \brief Escape a string for JSON.
576 *
577 * \param str the string to escape
578 *
579 * \return a newly allocated string that must be freed with G_free()
580 */
581static char *str_json_escape(const char *str)
582{
583 // Always duplicate the input string for code simplicity.
584 char *out = G_store(str);
585 // We test character presence to avoid duplicating the input string
586 // for every potential replacement.
587 // While formfeed and backspace are unlikely, we check for them to
588 // ensure valid JSON.
589 out = str_replace_free_buffer(out, '\\', "\\\\");
590 out = str_replace_free_buffer(out, '\r', "\\r");
591 out = str_replace_free_buffer(out, '\n', "\\n");
592 out = str_replace_free_buffer(out, '\t', "\\t");
593 out = str_replace_free_buffer(out, '\"', "\\\"");
594 out = str_replace_free_buffer(out, '\f', "\\f");
595 out = str_replace_free_buffer(out, '\b', "\\b");
596 return out;
597}
#define NULL
Definition ccmath.h:32
const char * G_program_name(void)
Return module name.
Definition progrm_nme.c:28
void G_free(void *)
Free allocated memory.
Definition gis/alloc.c:147
void void void void G_fatal_error(const char *,...) __attribute__((format(printf
char ** G_tokenize(const char *, const char *)
Tokenize string.
Definition gis/token.c:47
char * G_tempfile(void)
Returns a temporary file name.
Definition tempfile.c:62
void G_free_tokens(char **)
Free memory allocated to tokens.
Definition gis/token.c:197
int int G_strcasecmp(const char *, const char *)
String compare ignoring case (upper or lower)
Definition strings.c:47
char * G_chop(char *)
Chop leading and trailing white spaces.
Definition strings.c:332
char * G_store(const char *)
Copy string to allocated memory.
Definition strings.c:87
int G_strncasecmp(const char *, const char *, int)
String compare ignoring case (upper or lower) - limited number of characters.
Definition strings.c:69
char * G_str_replace(const char *, const char *, const char *)
Replace all occurrences of old_str in buffer with new_str.
Definition strings.c:189
int G_debug(int, const char *,...) __attribute__((format(printf
const char * G_mapset(void)
Get current mapset name.
Definition gis/mapset.c:33
#define _(str)
Definition glocale.h:10
struct state * st
Definition parser.c:104
void G__split_gisprompt(const char *gisprompt, char *age, char *element, char *desc)
Definition parser.c:1775
void check_create_import_opts(struct Option *, char *, FILE *)
void check_create_export_opts(struct Option *, char *, FILE *)
char * check_mapset_in_layer_name(char *, int)
char * G__json(void)
This function generates actinia JSON process chain building blocks from the command line arguments th...
Structure that stores flag info.
Definition gis.h:594
Structure that stores option information.
Definition gis.h:563