Loading...
Searching...
No Matches
parser_json.c
Go to the documentation of this file.
/*!
\file lib/gis/parser_json.c
\brief GIS Library - converts the command line arguments into actinia JSON
process chain building blocks
(C) 2018-2021 by the GRASS Development Team
This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.
\author Soeren Gebbert
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <grass/glocale.h>
#include <grass/gis.h>
#include "parser_local_proto.h"
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);
static char *str_json_escape(const char *str);
static char *str_replace_free_buffer(char *buffer, const char old_char,
const char *new_str);
/*!
\brief This function generates actinia JSON process chain building blocks
from the command line arguments that can be used in the actinia processing
API.
The following commands will create according JSON output:
r.slope.aspect
elevation="elevation@https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif"
slope="slope+GTiff" aspect="aspect+GTiff" --json
{
"module": "r.slope.aspect",
"id": "r.slope.aspect_1804289383",
"inputs":[
{"import_descr":
{"source":"https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif",
"type":"raster"}, "param": "elevation", "value": "elevation"},
{"param": "format", "value": "degrees"},
{"param": "precision", "value": "FCELL"},
{"param": "zscale", "value": "1.0"},
{"param": "min_slope", "value": "0.0"}
],
"outputs":[
{"export": {"format":"GTiff", "type":"raster"},
"param": "slope", "value": "slope"},
{"export": {"format":"GTiff", "type":"raster"},
"param": "aspect", "value": "aspect"}
]
}
v.out.ascii input="hospitals@PERMANENT" output="myfile+TXT" --json
{
"module": "v.out.ascii",
"id": "v.out.ascii_1804289383",
"inputs":[
{"param": "input", "value": "hospitals@PERMANENT"},
{"param": "layer", "value": "1"},
{"param": "type", "value": "point,line,boundary,centroid,area,face,kernel"},
{"param": "format", "value": "point"},
{"param": "separator", "value": "pipe"},
{"param": "precision", "value": "8"}
],
"outputs":[
{"export": {"format":"TXT", "type":"file"},
"param": "output", "value": "$file::myfile"}
]
}
v.info map="hospitals@PERMANENT" -c --json
{
"module": "v.info",
"id": "v.info_1804289383",
"flags":"c",
"inputs":[
{"param": "map", "value": "hospitals@PERMANENT"},
{"param": "layer", "value": "1"}
]
}
A process chain has the following form
{
'list': [{
'module': 'g.region',
'id': 'g_region_1',
'inputs': [{'import_descr': {'source':
'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif', 'type':
'raster'}, 'param': 'raster', 'value': 'elev_ned_30m_new'}], 'flags': 'p'
},
{
'module': 'r.slope.aspect',
'id': 'r_slope_aspect_1',
'inputs': [{'param': 'elevation',
'value': 'elev_ned_30m_new'}],
'outputs': [{'export': {'format': 'GTiff',
'type': 'raster'},
'param': 'slope',
'value': 'elev_ned_30m_new_slope'}],
'flags': 'a'},
{
'module': 'r.univar',
'id': 'r_univar_1',
'inputs': [{"import_descr": {"source": "LT52170762005240COA00",
"type": "landsat",
"landsat_atcor": "dos1"},
'param': 'map',
'value': 'LT52170762005240COA00_dos1.1'}],
'stdout': {'id': 'stats', 'format': 'kv', 'delimiter': '='},
'flags': 'a'
},
{
'module': 'exporter',
'id': 'exporter_1',
'outputs': [{'export': {'format': 'GTiff',
'type': 'raster'},
'param': 'map',
'value': 'LT52170762005240COA00_dos1.1'}]
},
{
"id": "ascii_out",
"module": "r.out.ascii",
"inputs": [{"param": "input",
"value": "elevation@PERMANENT"},
{"param": "precision", "value": "0"}],
"stdout": {"id": "elev_1", "format": "table", "delimiter": " "},
"flags": "h"
},
{
"id": "ascii_export",
"module": "r.out.ascii",
"inputs": [{"param": "input",
"value": "elevation@PERMANENT"}],
"outputs": [
{"export": {"type": "file", "format": "TXT"},
"param": "output",
"value": "$file::out1"}
]
},
{
"id": "raster_list",
"module": "g.list",
"inputs": [{"param": "type",
"value": "raster"}],
"stdout": {"id": "raster", "format": "list", "delimiter": "\n"}
},
{
"module": "r.what",
"id": "r_what_1",
"verbose": True,
"flags": "nfic",
"inputs": [
{
"param": "map",
"value": "landuse96_28m@PERMANENT"
},
{
"param": "coordinates",
"value": "633614.08,224125.12,632972.36,225382.87"
},
{
"param": "null_value",
"value": "null"
},
{
"param": "separator",
"value": "pipe"
}
],
"stdout": {"id": "sample", "format": "table", "delimiter": "|"}
}
],
'webhooks': {'update':
'http://business-logic.company.com/api/v1/actinia-update-webhook',
'finished':
'http://business-logic.company.com/api/v1/actinia-finished-webhook'},
'version': '1'
}
*/
char *G__json(void)
{
FILE *fp = stdout;
/*FILE *fp = NULL; */
char *file_name = NULL;
int c;
int random_int = rand();
int num_flags = 0;
int num_inputs = 0;
int num_outputs = 0;
int i = 0;
char age[KEYLENGTH];
char element[KEYLENGTH]; /*cell, file, grid3, vector */
char desc[KEYLENGTH];
file_name = G_tempfile();
/* fprintf(stderr, "Filename: %s\n", file_name); */
fp = fopen(file_name, "w+");
if (fp == NULL) {
fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
exit(EXIT_FAILURE);
}
if (st->n_flags) {
struct Flag *flag;
for (flag = &st->first_flag; flag; flag = flag->next_flag) {
if (flag->answer)
num_flags += 1;
;
}
}
/* Count input and output options */
if (st->n_opts) {
struct Option *opt;
for (opt = &st->first_option; opt; opt = opt->next_opt) {
if (opt->answer) {
if (opt->gisprompt) {
G__split_gisprompt(opt->gisprompt, age, element, desc);
/* fprintf(stderr, "age: %s element: %s desc: %s\n", age,
* element, desc); */
if (G_strncasecmp("new", age, 3) == 0) {
/*fprintf(fp, "new: %s\n", opt->gisprompt); */
num_outputs += 1;
}
else {
/*fprintf(fp, "%s\n", opt->gisprompt); */
num_inputs += 1;
}
}
else {
num_inputs += 1;
}
}
}
}
fprintf(fp, "{\n");
fprintf(fp, " \"module\": \"%s\",\n", G_program_name());
fprintf(fp, " \"id\": \"%s_%i\"", G_program_name(), random_int);
if (st->n_flags && num_flags > 0) {
struct Flag *flag;
fprintf(fp, ",\n");
fprintf(fp, " \"flags\":\"");
for (flag = &st->first_flag; flag; flag = flag->next_flag) {
if (flag->answer)
fprintf(fp, "%c", flag->key);
}
fprintf(fp, "\"");
}
/* Print the input options
*/
if (st->n_opts && num_inputs > 0) {
struct Option *opt;
i = 0;
fprintf(fp, ",\n");
fprintf(fp, " \"inputs\":[\n");
for (opt = &st->first_option; opt; opt = opt->next_opt) {
if (opt->gisprompt) {
G__split_gisprompt(opt->gisprompt, age, element, desc);
if (G_strncasecmp("new", age, 3) != 0) {
if (opt->answer) {
check_create_import_opts(opt, element, fp);
i++;
if (i < num_inputs) {
fprintf(fp, ",\n");
}
else {
fprintf(fp, "\n");
}
}
}
}
else if (opt->answer) {
/* Check for input options */
fprintf(fp, " {\"param\": \"%s\", ", opt->key);
char *escaped_value = str_json_escape(opt->answer);
fprintf(fp, "\"value\": \"%s\"}", escaped_value);
G_free(escaped_value);
i++;
if (i < num_inputs) {
fprintf(fp, ",\n");
}
else {
fprintf(fp, "\n");
}
}
}
fprintf(fp, " ]");
}
/* Print the output options
*/
if (st->n_opts && num_outputs > 0) {
struct Option *opt;
i = 0;
fprintf(fp, ",\n");
fprintf(fp, " \"outputs\":[\n");
for (opt = &st->first_option; opt; opt = opt->next_opt) {
if (opt->gisprompt) {
G__split_gisprompt(opt->gisprompt, age, element, desc);
if (G_strncasecmp("new", age, 3) == 0) {
if (opt->answer) {
check_create_export_opts(opt, element, fp);
i++;
if (i < num_outputs) {
fprintf(fp, ",\n");
}
else {
fprintf(fp, "\n");
}
}
}
}
}
fprintf(fp, " ]\n");
}
fprintf(fp, "}\n");
fclose(fp);
/* Print the file content to stdout */
fp = fopen(file_name, "r");
if (fp == NULL) {
fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
exit(EXIT_FAILURE);
}
c = fgetc(fp);
while (c != EOF) {
fprintf(stdout, "%c", c);
c = fgetc(fp);
}
fclose(fp);
return file_name;
}
/* \brief Check the provided answer and generate the import statement
dependent on the element type (cell, vector, grid3, file)
{'import_descr': {'source':
'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif', 'type':
'raster'}, 'param': 'map', 'value': 'elevation'}
*/
void check_create_import_opts(struct Option *opt, char *element, FILE *fp)
{
int i = 0, urlfound = 0;
int has_import = 0;
char **tokens;
G_debug(2, "tokenize opt string: <%s> with '@'", opt->answer);
tokens = G_tokenize(opt->answer, "@");
while (tokens[i]) {
G_chop(tokens[i]);
i++;
}
if (i > 2)
G_fatal_error(
_("Input string not understood: <%s>. Multiple '@' chars?"),
opt->answer);
if (i > 1) {
/* check if tokens[1] starts with an URL or name@mapset */
G_debug(2, "tokens[1]: <%s>", tokens[1]);
if (strncmp(tokens[1], "http://", 7) == 0 ||
strncmp(tokens[1], "https://", 8) == 0 ||
strncmp(tokens[1], "ftp://", 6) == 0) {
urlfound = 1;
G_debug(2, "URL found");
}
else {
urlfound = 0;
G_debug(2, "name@mapset found");
}
}
fprintf(fp, " {");
if (i > 1 && urlfound == 1) {
if (G_strncasecmp("cell", element, 4) == 0) {
fprintf(fp,
"\"import_descr\": {\"source\":\"%s\", "
"\"type\":\"raster\"},\n ",
tokens[1]);
has_import = 1;
}
else if (G_strncasecmp("file", element, 4) == 0) {
fprintf(fp,
"\"import_descr\": {\"source\":\"%s\", "
"\"type\":\"file\"},\n ",
tokens[1]);
has_import = 1;
}
else if (G_strncasecmp("vector", element, 4) == 0) {
fprintf(fp,
"\"import_descr\": {\"source\":\"%s\", "
"\"type\":\"vector\"},\n ",
tokens[1]);
has_import = 1;
}
}
fprintf(fp, "\"param\": \"%s\", ", opt->key);
/* In case of import the mapset must be removed always */
if (urlfound == 1) {
char *escaped_value =
str_json_escape(check_mapset_in_layer_name(tokens[0], has_import));
fprintf(fp, "\"value\": \"%s\"", escaped_value);
G_free(escaped_value);
}
else {
char *escaped_value = str_json_escape(
check_mapset_in_layer_name(opt->answer, has_import));
fprintf(fp, "\"value\": \"%s\"", escaped_value);
G_free(escaped_value);
};
fprintf(fp, "}");
G_free_tokens(tokens);
}
/* \brief Check the provided answer and generate the export statement
dependent on the element type (cell, vector, grid3, file)
"outputs": [
{"export": {"type": "file", "format": "TXT"},
"param": "output",
"value": "$file::out1"},
{'export': {'format': 'GTiff', 'type': 'raster'},
'param': 'map',
'value': 'LT52170762005240COA00_dos1.1'}
]
*/
void check_create_export_opts(struct Option *opt, char *element, FILE *fp)
{
int i = 0;
int has_file_export = 0;
char **tokens;
tokens = G_tokenize(opt->answer, "+");
while (tokens[i]) {
G_chop(tokens[i]);
i++;
}
fprintf(fp, " {");
if (i > 1) {
if (G_strncasecmp("cell", element, 4) == 0) {
fprintf(
fp,
"\"export\": {\"format\":\"%s\", \"type\":\"raster\"},\n ",
tokens[1]);
}
else if (G_strncasecmp("file", element, 4) == 0) {
fprintf(
fp,
"\"export\": {\"format\":\"%s\", \"type\":\"file\"},\n ",
tokens[1]);
has_file_export = 1;
}
else if (G_strncasecmp("vector", element, 4) == 0) {
fprintf(
fp,
"\"export\": {\"format\":\"%s\", \"type\":\"vector\"},\n ",
tokens[1]);
}
}
fprintf(fp, "\"param\": \"%s\", ", opt->key);
if (has_file_export == 1) {
char *escaped_value =
str_json_escape(check_mapset_in_layer_name(tokens[0], 1));
fprintf(fp, "\"value\": \"$file::%s\"", escaped_value);
G_free(escaped_value);
}
else {
char *escaped_value =
str_json_escape(check_mapset_in_layer_name(tokens[0], 1));
fprintf(fp, "\"value\": \"%s\"", escaped_value);
G_free(escaped_value);
}
fprintf(fp, "}");
G_free_tokens(tokens);
}
/*
\brief Check if the current mapset is present in the layer name and remove it
The flag always_remove tells this function to always remove all mapset names.
\return pointer to the layer name without the current mapset
*/
char *check_mapset_in_layer_name(char *layer_name, int always_remove)
{
int i = 0;
char **tokens;
const char *mapset;
mapset = G_mapset();
tokens = G_tokenize(layer_name, "@");
while (tokens[i]) {
G_chop(tokens[i]);
/* fprintf(stderr, "Token %i: %s\n", i, tokens[i]); */
i++;
}
if (always_remove == 1)
return tokens[0];
if (i > 1 && G_strcasecmp(mapset, tokens[1]) == 0)
return tokens[0];
return layer_name;
}
/** \brief Replace *old_char* with *new_str* and free the *buffer* if needed
*
* Replaces *old_char* (one character) with *new_str* (a string) in *buffer*
* and returns the new string. If no replacements were made, returns pointer
* to the original *buffer* unchanged.
*
* In any case, returned string should be freed with G_free(), while
* the original *buffer* should not be freed. The *buffer* must be a heap
* allocated string.
*
* \param buffer the string to make replacements in
* \param old_char the character to replace
* \param new_str the replacement string
*
* \return a newly allocated string or *buffer* if no replacements were made
*/
static char *str_replace_free_buffer(char *buffer, const char old_char,
const char *new_str)
{
// Check if the input string contains the character to replace to avoid
// allocation in G_str_replace.
if (strchr(buffer, old_char)) {
// The function takes strings, not chars.
char old_str[2] = {old_char, '\0'};
char *res = G_str_replace(buffer, old_str, new_str);
G_free(buffer);
return res;
}
return buffer;
}
/** \brief Escape a string for JSON.
*
* \param str the string to escape
*
* \return a newly allocated string that must be freed with G_free()
*/
static char *str_json_escape(const char *str)
{
// Always duplicate the input string for code simplicity.
char *out = G_store(str);
// We test character presence to avoid duplicating the input string
// for every potential replacement.
// While formfeed and backspace are unlikely, we check for them to
// ensure valid JSON.
out = str_replace_free_buffer(out, '\\', "\\\\");
out = str_replace_free_buffer(out, '\r', "\\r");
out = str_replace_free_buffer(out, '\n', "\\n");
out = str_replace_free_buffer(out, '\t', "\\t");
out = str_replace_free_buffer(out, '\"', "\\\"");
out = str_replace_free_buffer(out, '\f', "\\f");
out = str_replace_free_buffer(out, '\b', "\\b");
return out;
}
G_free
void G_free(void *buf)
Free allocated memory.
Definition alloc.c:147
NULL
#define NULL
Definition ccmath.h:32
G_debug
int G_debug(int level, const char *msg,...)
Print debugging message.
Definition debug.c:66
G_fatal_error
void G_fatal_error(const char *msg,...)
Print a fatal error message to stderr.
Definition gis/error.c:159
G_mapset
const char * G_mapset(void)
Get current mapset name.
Definition mapset.c:33
G__split_gisprompt
void G__split_gisprompt(const char *gisprompt, char *age, char *element, char *desc)
Definition parser.c:1775
check_create_import_opts
void check_create_import_opts(struct Option *, char *, FILE *)
Definition parser_json.c:369
check_create_export_opts
void check_create_export_opts(struct Option *, char *, FILE *)
Definition parser_json.c:458
check_mapset_in_layer_name
char * check_mapset_in_layer_name(char *, int)
Definition parser_json.c:519
G__json
char * G__json(void)
This function generates actinia JSON process chain building blocks from the command line arguments th...
Definition parser_json.c:193
G_program_name
const char * G_program_name(void)
Return module name.
Definition progrm_nme.c:28
G_strncasecmp
int G_strncasecmp(const char *x, const char *y, int n)
String compare ignoring case (upper or lower) - limited number of characters.
Definition strings.c:69
G_str_replace
char * G_str_replace(const char *buffer, const char *old_str, const char *new_str)
Replace all occurrences of old_str in buffer with new_str.
Definition strings.c:189
G_chop
char * G_chop(char *line)
Chop leading and trailing white spaces.
Definition strings.c:332
G_strcasecmp
int G_strcasecmp(const char *x, const char *y)
String compare ignoring case (upper or lower).
Definition strings.c:47
G_store
char * G_store(const char *s)
Copy string to allocated memory.
Definition strings.c:87
element
Definition lidar.h:85
G_tempfile
char * G_tempfile(void)
Returns a temporary file name.
Definition tempfile.c:62
G_free_tokens
void G_free_tokens(char **tokens)
Free memory allocated to tokens.
Definition token.c:197
G_tokenize
char ** G_tokenize(const char *buf, const char *delim)
Tokenize string.
Definition token.c:47