[GRASS-SVN] r39794 - grass/trunk/lib/gis

svn_grass at osgeo.org svn_grass at osgeo.org
Tue Nov 24 15:59:02 EST 2009


Author: huhabla
Date: 2009-11-24 15:59:01 -0500 (Tue, 24 Nov 2009)
New Revision: 39794

Modified:
   grass/trunk/lib/gis/parser.c
Log:
WPS ProcessDescription XML document generation added to parser.c

Modified: grass/trunk/lib/gis/parser.c
===================================================================
--- grass/trunk/lib/gis/parser.c	2009-11-23 22:13:15 UTC (rev 39793)
+++ grass/trunk/lib/gis/parser.c	2009-11-24 20:59:01 UTC (rev 39794)
@@ -70,6 +70,7 @@
  * (>=v2). Read the file COPYING that comes with GRASS for details.
  *
  * \author Original author CERL
+ * \author Soeren Gebbert added Dec. 2009 WPS ProcessDescription document 
  */
 
 #include <grass/config.h>
@@ -159,6 +160,46 @@
 static void usage_html(void);
 static void script(void);
 
+
+/* Defines and prototypes for WPS ProcessDescription XML document generation
+ */
+#define TYPE_OTHER -1
+#define TYPE_RASTER 0
+#define TYPE_VECTOR 1
+#define TYPE_PLAIN_TEXT 2
+#define TYPE_RANGE 3
+#define TYPE_LIST 4
+#define WPS_INPUT 0
+#define WPS_OUTPUT 1
+
+
+static void wps_print_ProcessDescriptions_begin();
+static void wps_print_ProcessDescriptions_end();
+static void wps_print_ProcessDescription_begin(int , int , const char *, const char *, const char *, const char **, int );
+static void wps_print_ProcessDescription_end();
+static void wps_print_DataInputs_begin();
+static void wps_print_DataInputs_end();
+static void wps_print_ProcessOutputs_begin();
+static void wps_print_ProcessOutputs_end();
+static void wps_print_mimetype_text_plain();
+static void wps_print_mimetype_raster_tiff();
+static void wps_print_mimetype_raster_png();
+static void wps_print_mimetype_raster_grass_binary();
+static void wps_print_mimetype_raster_grass_ascii();
+static void wps_print_mimetype_vector_gml310();
+static void wps_print_mimetype_vector_grass_ascii();
+static void wps_print_mimetype_vector_grass_binary();
+static void wps_print_ident_title_abstract(const char *, const char *, const char *);
+static void wps_print_complex_input(int , int , const char *, const char *, const char *, int , int );
+static void wps_print_complex_output(const char *, const char *, const char *, int );
+static void wps_print_comlpex_input_output(int , int , int , const char *, const char *, const char *, int , int );
+static void wps_print_literal_input_output(int , int , int , const char *,
+                                const char *, const char *, const char *, int ,
+                                const char **, int , const char *, int );
+/* Print the WPS ProcessDescription XML document to stdout */
+static void wps_print_ProcessDescription();
+
+
 /*!
  * \brief Disables the ability of the parser to operate interactively.
  *
@@ -915,6 +956,13 @@
 	    exit(EXIT_SUCCESS);
 	}
 
+	/* If first arg is "--wps-process-description" then print out
+	 * the wps process description of the task */
+	if (strcmp(argv[1], "--wps-process-description") == 0) {
+	    wps_print_ProcessDescription();
+	    exit(EXIT_SUCCESS);
+	}
+
 	/* If first arg is "--script" then then generate
 	 * g.parser boilerplate */
 	if (strcmp(argv[1], "--script") == 0) {
@@ -2666,3 +2714,566 @@
     st->module_info.keywords = (const char**)G_tokenize(keywords, ",");
     st->n_keys = st->n_keys_alloc = G_number_of_tokens((char **)st->module_info.keywords);
 }
+
+
+/**************************************************************************
+ *
+ * The remaining routines are all local (static) routines used to support
+ * the the creation of the WPS ProcessDescription document.
+ *
+ **************************************************************************/
+
+static void wps_print_ProcessDescription()
+{
+    struct Option *opt;
+    struct Flag *flag;
+    char *type;
+    char *s, *top;
+    const char *value = NULL;
+    int i;
+    char *encoding;
+    int new_prompt = 0;
+    int store = 1;
+    int status = 1;
+    const char *identifier = NULL;
+    const char *title = NULL;
+    const char *abstract = NULL;
+    const char **keywords = NULL;
+    int data_type, is_input, is_output;
+    int min = 0, max = 0;
+    int num_keywords = 0;
+    int found_output = 0;
+    new_prompt = uses_new_gisprompt();
+
+    /* gettext converts strings to encoding returned by nl_langinfo(CODESET) */
+
+#if defined(HAVE_LANGINFO_H)
+    encoding = nl_langinfo(CODESET);
+    if (!encoding || strlen(encoding) == 0) {
+	encoding = "UTF-8";
+    }
+#elif defined(__MINGW32__) && defined(USE_NLS)
+    encoding = locale_charset();
+    if (!encoding || strlen(encoding) == 0) {
+	encoding = "UTF-8";
+    }
+#else
+    encoding = "UTF-8";
+#endif
+
+    if (!st->pgm_name)
+	st->pgm_name = G_program_name();
+    if (!st->pgm_name)
+	st->pgm_name = "??";
+
+    /* the identifier of the process is the module name */
+    identifier = st->pgm_name;
+
+    if (st->module_info.description) {
+        title = st->module_info.description;
+        abstract = st->module_info.description;
+    }
+
+    if (st->module_info.keywords) {
+        keywords = st->module_info.keywords;
+        num_keywords = st->n_keys;
+    }
+
+    wps_print_ProcessDescriptions_begin();
+    /* store and status are supported as default. The WPS server should change this if nessessary */
+    wps_print_ProcessDescription_begin(store, status, identifier, title, abstract, keywords, num_keywords);
+    wps_print_DataInputs_begin();
+
+    /* We parse only the inputs at the beginning */
+    if (st->n_opts) {
+	opt = &st->first_option;
+	while (opt != NULL) {
+
+            identifier = NULL;
+            title = NULL;
+            abstract = NULL;
+            keywords = NULL;
+            num_keywords = 0;
+            value = NULL;
+            is_input = 1;
+            data_type = TYPE_OTHER;
+
+	    if (opt->gisprompt) {
+		const char *atts[] = { "age", "element", "prompt", NULL };
+		top = G_calloc(strlen(opt->gisprompt) + 1, 1);
+		strcpy(top, opt->gisprompt);
+		s = strtok(top, ",");
+		for (i = 0; s != NULL && atts[i] != NULL; i++) {
+
+                    char *token = G_store(s);
+
+                    /* we print only input parameter, sort out the output parameter */
+                    if(strcmp(token, "new") == 0)
+                        is_input = 0;
+                    if(strcmp(token, "raster") == 0)
+                    {
+                        data_type = TYPE_RASTER;
+                    }
+                    if(strcmp(token, "vector") == 0)
+                    {
+                        data_type = TYPE_VECTOR;
+                    }
+                    s = strtok(NULL, ",");
+                    G_free(token);
+		}
+		G_free(top);
+	    }
+            /* We have an input option */
+            if(is_input == 1)
+            {
+                switch (opt->type) {
+                case TYPE_INTEGER:
+                    type = "integer";
+                    break;
+                case TYPE_DOUBLE:
+                    type = "float";
+                    break;
+                case TYPE_STRING:
+                    type = "string";
+                    break;
+                default:
+                    type = "string";
+                    break;
+                }
+
+                identifier = opt->key;
+                if(opt->required == YES)
+                    min = 1;
+                else
+                    min = 0;
+
+                if(opt->multiple == YES)
+                    max = 1024;
+                else
+                    max = 1;
+
+                if (opt->description) {
+                    title = opt->description;
+                    abstract = opt->description;
+                }
+                if (opt->def) {
+                    value = opt->def;
+                }
+                if (opt->options) {
+                    /* TODO:
+                     * add something like
+                     *       <range min="xxx" max="xxx"/>
+                     * to <values> */
+                    i = 0;
+                    while (opt->opts[i]) {
+                        i++;
+                    }
+                    keywords = opt->opts;
+                    num_keywords = i;
+                }
+
+                if(data_type == TYPE_RASTER || data_type == TYPE_VECTOR)
+                {
+                    /* 2048 is the maximum size of the map in mega bytes */
+                    wps_print_complex_input(min, max, identifier, title, abstract, 2048, data_type);
+                }
+                else
+                {
+                    /* The keyword array is missused for options, type means the type of the value (integer, float ... )*/
+                    wps_print_literal_input_output(WPS_INPUT, min, max, identifier, title, abstract, type, 0, keywords, num_keywords, value, TYPE_OTHER);
+                }
+            }
+	    opt = opt->next_opt;
+	}
+    }
+
+    /* Flags are always input options and can be false or true (boolean) */
+    if (st->n_flags) {
+	flag = &st->first_flag;
+	while (flag != NULL) {
+
+            /* The identifier is the flag "-x" */
+            char* ident = (char*)G_calloc(3, sizeof(char));
+            ident[0] = '-';
+            ident[1] = flag->key;
+            ident[2] = '\0';
+            title = NULL;
+            abstract = NULL;
+
+	    if (flag->description) {
+                title = flag->description;
+                abstract = flag->description;
+	    }
+            const char *val[] = {"true","false"};
+            wps_print_literal_input_output(WPS_INPUT, 0, 1, ident, title, abstract, "boolean", 0, val, 2, "false", TYPE_OTHER);
+	    flag = flag->next_flag;
+	}
+    }
+
+    /* End of inputs */
+    wps_print_DataInputs_end();
+    /* Start of the outputs */
+    wps_print_ProcessOutputs_begin();
+
+    found_output = 0;
+
+    /*parse the ouput. only raster and vector map and stdout are supported */
+    if (st->n_opts) {
+	opt = &st->first_option;
+	while (opt != NULL) {
+
+            identifier = NULL;
+            title = NULL;
+            abstract = NULL;
+            value = NULL;
+            is_output = 0;
+            data_type = TYPE_OTHER;
+
+	    if (opt->gisprompt) {
+		const char *atts[] = { "age", "element", "prompt", NULL };
+		top = G_calloc(strlen(opt->gisprompt) + 1, 1);
+		strcpy(top, opt->gisprompt);
+		s = strtok(top, ",");
+		for (i = 0; s != NULL && atts[i] != NULL; i++) {
+
+                    char *token = G_store(s);
+
+                    /* we print only the output parameter */
+                    if(strcmp(token, "new") == 0)
+                        is_output = 1;
+                    if(strcmp(token, "raster") == 0)
+                    {
+                        data_type = TYPE_RASTER;
+                    }
+                    if(strcmp(token, "vector") == 0)
+                    {
+                        data_type = TYPE_VECTOR;
+                    }
+                    s = strtok(NULL, ",");
+                    G_free(token);
+		}
+		G_free(top);
+	    }
+            /* Only single module output is supported */
+            if(is_output == 1 && opt->multiple == NO)
+            {
+                identifier = opt->key;
+                if (opt->description) {
+                    title = opt->description;
+                    abstract = opt->description;
+                }
+
+                /* Only raster and vector output is supported by option */
+                if(data_type == TYPE_RASTER || data_type == TYPE_VECTOR)
+                {
+                    wps_print_complex_output(identifier, title, abstract, data_type);
+                    found_output = 1;
+                }
+            }
+	    opt = opt->next_opt;
+	}
+        /* we assume the computatuon output on stdout, if no raster/vector output was found*/
+        if(found_output == 0)
+            wps_print_complex_output("stdout", "Module output on stdout", "The output of the module written to stdout", TYPE_PLAIN_TEXT);
+    }
+
+    wps_print_ProcessOutputs_end();
+    wps_print_ProcessDescription_end();
+    wps_print_ProcessDescriptions_end();
+}
+
+/* ************************************************************************** */
+
+static void wps_print_ProcessDescriptions_begin()
+{
+    fprintf(stdout, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+    fprintf(stdout, "<wps:ProcessDescriptions xmlns:wps=\"http://www.opengis.net/wps/1.0.0\"\n");
+    fprintf(stdout, "xmlns:ows=\"http://www.opengis.net/ows/1.1\"\n");
+    fprintf(stdout, "xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
+    fprintf(stdout, "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
+    fprintf(stdout, "xsi:schemaLocation=\"http://www.opengis.net/wps/1.0.0/wpsDescribeProcess_response.xsd\" service=\"WPS\" version=\"1.0.0\" xml:lang=\"en-US\"> \n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_ProcessDescriptions_end()
+{
+    fprintf(stdout,"</wps:ProcessDescriptions>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_ProcessDescription_begin(int store, int status, const char *identifier,
+                                               const char *title, const char *abstract,
+                                               const char **keywords, int num_keywords)
+{
+    int i;
+
+    fprintf(stdout,"\t<ProcessDescription wps:processVersion=\"1\" storeSupported=\"%s\" statusSupported=\"%s\">\n", (store?"true":"false"), (status?"true":"false"));
+    wps_print_ident_title_abstract(identifier, title, abstract);
+    for(i = 0; i < num_keywords; i++)
+    {
+        fprintf(stdout,"\t\t<ows:Metadata xlink:title=\"%s\" />\n", keywords[i]);
+    }
+}
+
+/* ************************************************************************** */
+
+static void wps_print_ProcessDescription_end()
+{
+    fprintf(stdout,"\t</ProcessDescription>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_DataInputs_begin()
+{
+    fprintf(stdout,"\t\t<DataInputs>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_DataInputs_end()
+{
+    fprintf(stdout,"\t\t</DataInputs>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_ProcessOutputs_begin()
+{
+    fprintf(stdout,"\t\t<ProcessOutputs>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_ProcessOutputs_end()
+{
+    fprintf(stdout,"\t\t</ProcessOutputs>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_complex_input(int min, int max, const char *identifier, const char *title, const char *abstract, int megs, int type)
+{
+    wps_print_comlpex_input_output(WPS_INPUT, min, max, identifier, title, abstract, megs, type);
+}
+
+/* ************************************************************************** */
+
+static void wps_print_complex_output(const char *identifier, const char *title, const char *abstract, int type)
+{
+    wps_print_comlpex_input_output(WPS_OUTPUT, 0, 0, identifier, title, abstract, 0, type);
+}
+
+/* ************************************************************************** */
+
+static void wps_print_comlpex_input_output(int inout_type, int min, int max, const char *identifier, const char *title, const char *abstract, int megs, int type)
+{
+    if(inout_type == WPS_INPUT)
+        fprintf(stdout,"\t\t\t<Input minOccurs=\"%i\" maxOccurs=\"%i\">\n", min, max);
+    else if(inout_type == WPS_OUTPUT)
+        fprintf(stdout,"\t\t\t<Output>\n");
+
+    wps_print_ident_title_abstract(identifier, title, abstract);
+
+    if(inout_type == WPS_INPUT)
+        fprintf(stdout,"\t\t\t\t<ComplexData maximumMegabytes=\"%i\">\n", megs);
+    else if(inout_type == WPS_OUTPUT)
+        fprintf(stdout,"\t\t\t\t<ComplexOutput>\n");
+
+    fprintf(stdout,"\t\t\t\t\t<Default>\n");
+    if(type == TYPE_RASTER)
+    {
+            wps_print_mimetype_raster_tiff();
+    }
+    else if(type == TYPE_VECTOR)
+    {
+            wps_print_mimetype_vector_gml310();
+    }
+    else if(type == TYPE_PLAIN_TEXT)
+    {
+            wps_print_mimetype_text_plain();
+    }
+    fprintf(stdout,"\t\t\t\t\t</Default>\n");
+    fprintf(stdout,"\t\t\t\t\t<Supported>\n");
+    if(type == TYPE_RASTER)
+    {
+            wps_print_mimetype_raster_tiff();
+            wps_print_mimetype_raster_png();
+            wps_print_mimetype_raster_grass_ascii();
+            wps_print_mimetype_raster_grass_binary();
+    }
+    else if(type == TYPE_VECTOR)
+    {
+            wps_print_mimetype_vector_gml310();
+            wps_print_mimetype_vector_grass_ascii();
+            wps_print_mimetype_vector_grass_binary();
+    }
+    else if(type == TYPE_PLAIN_TEXT)
+    {
+            wps_print_mimetype_text_plain();
+    }
+    fprintf(stdout,"\t\t\t\t\t</Supported>\n");
+
+    if(inout_type == WPS_INPUT)
+        fprintf(stdout,"\t\t\t\t</ComplexData>\n");
+    else if(inout_type == WPS_OUTPUT)
+        fprintf(stdout,"\t\t\t\t</ComplexOutput>\n");
+
+    if(inout_type == WPS_INPUT)
+        fprintf(stdout,"\t\t\t</Input>\n");
+    else if(inout_type == WPS_OUTPUT)
+        fprintf(stdout,"\t\t\t</Output>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_ident_title_abstract(const char *identifier, const char *title, const char *abstract)
+{
+    if(identifier)
+        fprintf(stdout,"\t\t\t\t<ows:Identifier>%s</ows:Identifier>\n", identifier);
+
+    if(title)
+        fprintf(stdout,"\t\t\t\t<ows:Title>%s</ows:Title>\n", title);
+
+    if(abstract)
+        fprintf(stdout,"\t\t\t\t<ows:Abstract>%s</ows:Abstract>\n", abstract);
+}
+
+/* ************************************************************************** */
+
+static void wps_print_literal_input_output(int inout_type, int min, int max, const char *identifier,
+                                const char *title, const char *abstract, const char *datatype, int unitofmesure,
+                                const char **choices, int num_choices, const char *default_value, int type)
+{
+    int i;
+
+    if(inout_type == WPS_INPUT)
+        fprintf(stdout,"\t\t\t<Input minOccurs=\"%i\" maxOccurs=\"%i\">\n", min, max);
+    else if(inout_type == WPS_OUTPUT)
+        fprintf(stdout,"\t\t\t<Output>\n");
+
+    wps_print_ident_title_abstract(identifier, title, abstract);
+
+    fprintf(stdout,"\t\t\t\t<LiteralData>\n");
+
+    if(datatype)
+        fprintf(stdout,"\t\t\t\t\t<ows:DataType ows:reference=\"xs:%s\">%s</ows:DataType>\n", datatype, datatype);
+
+    if(unitofmesure)
+    {
+        fprintf(stdout,"\t\t\t\t\t<UOMs>\n");
+        fprintf(stdout,"\t\t\t\t\t<Default>\n");
+        fprintf(stdout,"\t\t\t\t\t\t<ows:UOM>meters</ows:UOM>\n");
+        fprintf(stdout,"\t\t\t\t\t</Default>\n");
+        fprintf(stdout,"\t\t\t\t\t<Supported>\n");
+        fprintf(stdout,"\t\t\t\t\t\t<ows:UOM>meters</ows:UOM>\n");
+        fprintf(stdout,"\t\t\t\t\t</Supported>\n");
+        fprintf(stdout,"\t\t\t\t\t</UOMs>\n");
+    }
+    if(num_choices == 0 || choices == NULL)
+        fprintf(stdout,"\t\t\t\t\t<ows:AnyValue/>\n");
+    else
+    {
+        fprintf(stdout,"\t\t\t\t\t<ows:AllowedValues>\n");
+        if(type == TYPE_RANGE && num_choices > 1)
+        {
+        fprintf(stdout,"\t\t\t\t\t\t<ows:Range ows:rangeClosure=\"%s\">\n", "0");
+        fprintf(stdout,"\t\t\t\t\t\t\t<ows:MinimumValue>%s</ows:MinimumValue>\n", choices[0]);
+        fprintf(stdout,"\t\t\t\t\t\t\t<ows:MaximumValue>%s</ows:MaximumValue>\n", choices[1]);
+        fprintf(stdout,"\t\t\t\t\t\t</ows:Range>\n");
+        }
+        else
+        {
+            for(i = 0; i < num_choices; i++)
+            {
+                fprintf(stdout,"\t\t\t\t\t\t<ows:Value>%s</ows:Value>\n", choices[i]);
+            }
+        }
+        fprintf(stdout,"\t\t\t\t\t</ows:AllowedValues>\n");
+    }
+
+    if(default_value)
+        fprintf(stdout,"\t\t\t\t\t<DefaultValue>%s</DefaultValue>\n", default_value);
+    fprintf(stdout,"\t\t\t\t</LiteralData>\n");
+
+
+    if(inout_type == WPS_INPUT)
+        fprintf(stdout,"\t\t\t</Input>\n");
+    else if(inout_type == WPS_OUTPUT)
+        fprintf(stdout,"\t\t\t</Output>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_mimetype_text_plain()
+{
+    fprintf(stdout,"\t\t\t\t\t\t<Format>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<MimeType>text/plain</MimeType>\n");
+    fprintf(stdout,"\t\t\t\t\t\t</Format>\n");
+}
+/* ************************************************************************** */
+
+static void wps_print_mimetype_raster_tiff()
+{
+    fprintf(stdout,"\t\t\t\t\t\t<Format>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<MimeType>image/tiff</MimeType>\n");
+    fprintf(stdout,"\t\t\t\t\t\t</Format>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_mimetype_raster_png()
+{
+    fprintf(stdout,"\t\t\t\t\t\t<Format>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<MimeType>image/png</MimeType>\n");
+    fprintf(stdout,"\t\t\t\t\t\t</Format>\n");
+}
+
+/* *** Native GRASS raster format urn:grass:raster:location/mapset/raster *** */
+
+static void wps_print_mimetype_raster_grass_binary()
+{
+    fprintf(stdout,"\t\t\t\t\t\t<Format>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<MimeType>application/grass-raster-binary</MimeType>\n");
+    fprintf(stdout,"\t\t\t\t\t\t</Format>\n");
+}
+
+/* *** GRASS raster maps exported via r.out.ascii ************************** */
+
+static void wps_print_mimetype_raster_grass_ascii()
+{
+    fprintf(stdout,"\t\t\t\t\t\t<Format>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<MimeType>application/grass-raster-ascii</MimeType>\n");
+    fprintf(stdout,"\t\t\t\t\t\t</Format>\n");
+}
+
+/* ************************************************************************** */
+
+static void wps_print_mimetype_vector_gml310()
+{
+    fprintf(stdout,"\t\t\t\t\t\t<Format>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<MimeType>text/xml</MimeType>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<Encoding>UTF-8</Encoding>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<Schema>http://schemas.opengis.net/gml/3.1.0/polygon.xsd</Schema>\n");
+    fprintf(stdout,"\t\t\t\t\t\t</Format>\n");
+}
+
+/* *** GRASS vector format exported via v.out.ascii ************************** */
+
+static void wps_print_mimetype_vector_grass_ascii()
+{
+    fprintf(stdout,"\t\t\t\t\t\t<Format>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<MimeType>application/grass-vector-ascii</MimeType>\n");
+    fprintf(stdout,"\t\t\t\t\t\t</Format>\n");
+}
+
+/* *** Native GRASS vector format urn:grass:vector:location/mapset/vector *** */
+
+static void wps_print_mimetype_vector_grass_binary()
+{
+    fprintf(stdout,"\t\t\t\t\t\t<Format>\n");
+    fprintf(stdout,"\t\t\t\t\t\t\t<MimeType>application/grass-vector-binary</MimeType>\n");
+    fprintf(stdout,"\t\t\t\t\t\t</Format>\n");
+}



More information about the grass-commit mailing list