GRASS 8 Programmer's Manual  8.5.0dev(2025)-9b240c893d
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 
23 void check_create_import_opts(struct Option *, char *, FILE *);
24 void check_create_export_opts(struct Option *, char *, FILE *);
25 char *check_mapset_in_layer_name(char *, int);
26 static char *str_json_escape(const char *str);
27 static 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  */
193 char *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);
216  exit(EXIT_FAILURE);
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);
301  G_free(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);
349  exit(EXIT_FAILURE);
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  */
369 void check_create_import_opts(struct Option *opt, char *element, FILE *fp)
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);
433  G_free(escaped_value);
434  }
435  else {
436  char *escaped_value = str_json_escape(
437  check_mapset_in_layer_name(opt->answer, has_import));
438  fprintf(fp, "\"value\": \"%s\"", escaped_value);
439  G_free(escaped_value);
440  };
441  fprintf(fp, "}");
442 
443  G_free_tokens(tokens);
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  */
458 void check_create_export_opts(struct Option *opt, char *element, FILE *fp)
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);
499  G_free(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);
505  G_free(escaped_value);
506  }
507  fprintf(fp, "}");
508 
509  G_free_tokens(tokens);
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  */
519 char *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  */
560 static 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  */
581 static 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
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_str_replace(const char *, const char *, const char *)
Replace all occurrences of old_str in buffer with new_str.
Definition: strings.c:189
char * G_tempfile(void)
Returns a temporary file name.
Definition: tempfile.c:62
const char * G_mapset(void)
Get current mapset name.
Definition: gis/mapset.c:33
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
int G_strncasecmp(const char *, const char *, int)
String compare ignoring case (upper or lower) - limited number of characters.
Definition: strings.c:69
const char * G_program_name(void)
Return module name.
Definition: progrm_nme.c:28
char * G_chop(char *)
Chop leading and trailing white spaces.
Definition: strings.c:332
int G_debug(int, const char *,...) __attribute__((format(printf
char * G_store(const char *)
Copy string to allocated memory.
Definition: strings.c:87
char ** G_tokenize(const char *, const char *)
Tokenize string.
Definition: gis/token.c:47
#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 *)
Definition: parser_json.c:369
void check_create_export_opts(struct Option *, char *, FILE *)
Definition: parser_json.c:458
char * G__json(void)
This function generates actinia JSON process chain building blocks from the command line arguments th...
Definition: parser_json.c:193
char * check_mapset_in_layer_name(char *, int)
Definition: parser_json.c:519
Structure that stores flag info.
Definition: gis.h:594
struct Flag * next_flag
Definition: gis.h:603
char key
Definition: gis.h:595
char answer
Definition: gis.h:596
Structure that stores option information.
Definition: gis.h:563
const char * key
Definition: gis.h:564
struct Option * next_opt
Definition: gis.h:580
const char * gisprompt
Definition: gis.h:581
char * answer
Definition: gis.h:577
Definition: lidar.h:85