eccodes/tools/grib_options.c

501 lines
18 KiB
C
Raw Normal View History

2013-03-25 12:04:10 +00:00
/*
2020-01-28 14:32:34 +00:00
* (C) Copyright 2005- ECMWF.
2013-03-25 12:04:10 +00:00
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
*
* In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
* virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
*/
/*
* C Implementation: grib_options
*
*/
#include "grib_tools.h"
2020-01-22 13:10:59 +00:00
extern char* optarg;
extern int optind;
2015-02-18 17:42:02 +00:00
#ifdef ECCODES_ON_WINDOWS
2013-03-25 14:23:07 +00:00
#include "wingetopt.h"
#endif
2020-01-22 13:10:59 +00:00
static const char* names[] = { "parameter", "vertical", "geography", "data", "mars", "local" };
static int names_count = 6;
2013-03-25 12:04:10 +00:00
/* id,args,help */
2020-01-22 13:10:59 +00:00
static grib_options_help grib_options_help_list[] = {
{ "a", 0, "Dump aliases.\n" },
{ "b:", "key,key,...",
"\n\t\tAll the keys in this list are skipped in the comparison. Bit-by-bit compare on.\n" },
{ "B:", "'order by' directive",
"\n\t\tOrder by. The output will be ordered according to the 'order by' directive."
"\n\t\tExample: \"step:i asc, centre desc\" (step numeric ascending and centre descending)\n" },
{ "c:", "key[:i|d|s|n],key[:i|d|s|n],...",
"\n\t\tOnly the listed keys or namespaces (:n) are compared. The optional letter after the colon is used "
"\n\t\tto force the type in the comparison: i->integer, d->float, s->string, n->namespace."
"\n\t\tSee -a option. Incompatible with -H option.\n" },
{ "d:", "value",
"\n\t\tSet all the data values to \"value\".\n" },
{ "e:", "tolerance", "\n\t\tOnly values whose difference is more than tolerance are considered different.\n" },
{ "f", 0, "Force. Force the execution not to fail on error.\n" },
{ "F:", "format", "\n\t\tC style format for floating-point values.\n" },
{ "g", 0, "Copy GTS header. \n" },
{ "G", 0, "GRIBEX compatibility mode.\n" },
{ "i:", "index",
"\n\t\tData value corresponding to the given index is printed.\n" },
{ "j", 0, "JSON mode (JavaScript Object Notation).\n" },
{ "l:", "Latitude,Longitude[,MODE,file]",
"\n\t\tValue close to the point of a Latitude,Longitude."
"\n\t\tAllowed values for MODE are:"
"\n\t\t 4 (4 values in the nearest points are printed) Default"
"\n\t\t 1 (the value at the nearest point is printed)"
"\n\t\tfile (file is used as mask. The closer point with mask value>=0.5 is printed)\n" },
{ "n:", "namespace",
"\n\t\tAll the keys belonging to namespace are printed.\n" },
{ "m", 0, "Mars keys are printed.\n" },
{ "o:", "output_file",
"\n\t\tOutput is written to output_file."
"\n\t\tIf an output file is required and -o is not used, the"
" output is written to 'filter.out'\n" },
{ "p:", "key[:{s|d|i}],key[:{s|d|i}],...",
"\n\t\tDeclaration of keys to print."
"\n\t\tFor each key a string (key:s), a double (key:d) or an integer (key:i)"
"\n\t\ttype can be requested. Default type is string.\n" },
{ "q", 0, "Quiet.\n" },
{ "r", 0,
"Repack data. Sometimes after setting some keys involving properties"
"\n\t\tof the packing algorithm a repacking of data is needed."
"\n\t\tThis repacking is performed setting this -r option.\n" },
{ "s:", "key[:{s|d|i}]=value,key[:{s|d|i}]=value,...",
"\n\t\tKey/values to set."
"\n\t\tFor each key a string (key:s), a double (key:d) or an integer (key:i)"
"\n\t\ttype can be defined. By default the native type is set.\n" },
{ "t", 0, "Print type information.\n" },
{ "w:", "key[:{s|d|i}]{=|!=}value,key[:{s|d|i}]{=|!=}value,...",
"\n\t\tWhere clause."
"\n\t\tMessages are processed only if they match all the"
" key/value constraints."
"\n\t\tA valid constraint is of type key=value or key!=value."
"\n\t\tFor each key a string (key:s), a double (key:d) or"
" an integer (key:i)\n\t\ttype can be specified. Default type is string."
"\n\t\tIn the value you can also use the forward-slash character '/' to specify an OR condition (i.e. a logical disjunction)"
"\n\t\tNote: only one -w clause is allowed.\n" },
{ "v", 0, "Verbose.\n" },
{ "7", 0, "Does not fail when the message has wrong length\n" },
{ "A:", "absolute error\n",
"\tCompare floating-point values using the absolute error as tolerance.\n\t\tDefault is absolute error=0\n" },
{ "C", 0, "C code mode. A C code program generating the message is dumped.\n" },
{ "D", 0, "Debug mode.\n" },
{ "H", 0, "Print octet content in hexadecimal format.\n" },
{ "M", 0, "Multi-field support off. Turn off support for multiple fields in single GRIB message.\n" },
{ "O", 0, "Octet mode. WMO documentation style dump.\n" },
{ "P:", "key[:{s|d|i}],key[:{s|d|i}],...",
"\n\t\tAs -p adding the declared keys to the default list.\n" },
{ "R:", "key1=relative_error1,key2=relative_error2,...\n",
"\tCompare floating-point values using the relative error as tolerance."
"\n\t\tkey1=relative_error1 will compare key1 using relative_error1."
"\n\t\tall=relative_error will compare all the floating-point keys using relative_error. Default all=0.\n" },
{ "S", 0,
"Strict. Only messages matching all the constraints are copied to"
"\n\t\tthe output file\n" },
{ "T:", "T | B | M | A", "Message type. T->GTS, B->BUFR, M->METAR (Experimental), A->Any (Experimental).\n\t\t\t\tThe input file is interpreted according to the message type.\n" },
{ "V", 0, "Version.\n" },
{ "W:", "width", "\n\t\tMinimum width of each column in output. Default is 10.\n" },
{ "X:", "offset", "\n\t\tInput file offset in bytes. Processing of the input file will start from \"offset\".\n" },
{ "x", 0, "Fast parsing option, only headers are loaded.\n" },
{ "k:", "key1,key2,...",
"\n\t\tSpecify a list of keys to index on. By default the input files are indexed on the MARS keys."
"\n\t\tFor each key a string (key:s) or a double (key:d) or an integer (key:i)"
"\n\t\ttype can be requested.\n" }
2013-03-25 12:04:10 +00:00
};
2020-01-22 13:10:59 +00:00
static int grib_options_help_count = sizeof(grib_options_help_list) / sizeof(grib_options_help);
2013-03-25 12:04:10 +00:00
2013-03-25 14:23:07 +00:00
2018-03-12 18:20:27 +00:00
void usage(void)
{
2020-01-22 13:10:59 +00:00
int i = 0;
printf("\nNAME \t%s\n\n", grib_tool_name);
printf("DESCRIPTION\n\t%s\n\n", grib_tool_description);
printf("USAGE \n\t%s %s\n\n", grib_tool_name, grib_tool_usage);
2014-01-10 14:07:02 +00:00
printf("OPTIONS\n");
2020-01-22 13:10:59 +00:00
for (i = 0; i < grib_options_count; i++) {
2014-01-10 14:07:02 +00:00
if (grib_options[i].command_line)
2020-01-22 13:10:59 +00:00
printf("\t-%c %s\t%s", grib_options[i].id[0],
grib_options_get_args(grib_options[i].id),
grib_options_get_help(grib_options[i].id));
2014-01-10 14:07:02 +00:00
}
printf("\n\n");
exit(1);
2013-03-25 14:23:07 +00:00
}
char* grib_options_get_option(const char* id)
{
2020-01-22 13:10:59 +00:00
int i = 0;
for (i = 0; i < grib_options_count; i++) {
if (!strcmp(id, grib_options[i].id))
2014-01-10 14:07:02 +00:00
return grib_options[i].value;
}
return NULL;
2013-03-25 12:04:10 +00:00
}
int grib_options_command_line(const char* id)
{
2020-01-22 13:10:59 +00:00
int i = 0;
for (i = 0; i < grib_options_count; i++) {
if (!strcmp(id, grib_options[i].id))
2014-01-10 14:07:02 +00:00
return grib_options[i].command_line;
}
return 0;
2013-03-25 12:04:10 +00:00
}
int grib_options_on(const char* id)
{
2020-01-22 13:10:59 +00:00
int i = 0;
for (i = 0; i < grib_options_count; i++) {
if (!strcmp(id, grib_options[i].id))
2014-01-10 14:07:02 +00:00
return grib_options[i].on;
}
return 0;
2013-03-25 12:04:10 +00:00
}
2020-01-22 13:10:59 +00:00
int grib_get_runtime_options(int argc, char** argv, grib_runtime_options* options)
{
2020-01-22 13:10:59 +00:00
int i = 0, c = 0;
char* optstr = (char*)calloc(1, 2 * grib_options_count * sizeof(char));
2014-01-10 14:07:02 +00:00
2020-01-22 13:10:59 +00:00
if (!optstr)
return GRIB_OUT_OF_MEMORY;
2014-01-10 14:07:02 +00:00
2020-01-22 13:10:59 +00:00
for (i = 0; i < grib_options_count; i++)
2014-01-10 14:07:02 +00:00
if (grib_options[i].command_line)
2020-01-22 13:10:59 +00:00
strncat(optstr, grib_options[i].id, 2);
while ((c = getopt(argc, argv, optstr)) != -1) {
if (c == '?')
usage();
i = 0;
while (i < grib_options_count && grib_options[i].id[0] != c)
i++;
grib_options[i].on = 1;
if (grib_options[i].id[1] == ':')
grib_options[i].value = optarg;
2014-01-10 14:07:02 +00:00
}
free(optstr);
return 0;
2013-03-25 12:04:10 +00:00
}
2020-01-22 13:10:59 +00:00
int grib_process_runtime_options(grib_context* context, int argc, char** argv, grib_runtime_options* options)
{
2020-01-22 13:10:59 +00:00
int i = 0, ret = 0;
int has_output = 0;
int has_input_extra = 0, nfiles = 0;
char *karg = NULL, *warg = NULL, *sarg = NULL, *barg = NULL;
2014-01-10 14:07:02 +00:00
if (grib_options_on("V")) {
printf("\necCodes Version ");
2014-01-10 14:07:02 +00:00
grib_print_api_version(stdout);
printf("\n\n");
exit(0);
}
2013-03-25 12:04:10 +00:00
2014-01-10 14:07:02 +00:00
if (grib_options_on("B:"))
2020-01-22 13:10:59 +00:00
options->orderby = grib_options_get_option("B:");
2013-03-25 12:04:10 +00:00
2020-01-22 13:10:59 +00:00
if (grib_options_on("x"))
options->headers_only = 1;
else
options->headers_only = 0;
2013-03-25 14:23:07 +00:00
2014-01-10 14:07:02 +00:00
if (grib_options_on("T:")) {
2020-01-22 13:10:59 +00:00
char* x = grib_options_get_option("T:");
if (*x == 'T')
options->mode = MODE_GTS;
else if (*x == 'B')
options->mode = MODE_BUFR;
else if (*x == 'M')
options->mode = MODE_METAR;
else if (*x == 'F')
options->mode = MODE_TAF;
else if (*x == 'A')
options->mode = MODE_ANY;
else
options->mode = MODE_GRIB;
2014-01-10 14:07:02 +00:00
}
2013-03-25 12:04:10 +00:00
2014-01-10 14:07:02 +00:00
if (grib_options_on("F:"))
2020-01-22 13:10:59 +00:00
options->format = grib_options_get_option("F:");
2014-01-10 14:07:02 +00:00
else
2020-01-22 13:10:59 +00:00
options->format = strdup("%g");
2013-03-25 12:04:10 +00:00
2014-01-10 14:07:02 +00:00
if (grib_options_on("i:")) {
2020-01-22 13:10:59 +00:00
options->index_on = 1;
options->index = atoi(grib_options_get_option("i:"));
2014-01-10 14:07:02 +00:00
}
2013-03-25 12:04:10 +00:00
2014-01-10 14:07:02 +00:00
if (grib_options_on("l:"))
2020-01-22 13:10:59 +00:00
options->latlon = grib_options_get_option("l:");
2013-03-25 12:04:10 +00:00
2020-01-22 13:10:59 +00:00
if (grib_options_on("j"))
options->json_output = 1;
else
options->json_output = 0;
2016-12-20 10:43:36 +00:00
if (grib_options_on("X:"))
2020-01-22 13:10:59 +00:00
options->infile_offset = atol(grib_options_get_option("X:"));
2015-02-18 17:42:02 +00:00
#ifndef ECCODES_ON_WINDOWS
/* Check at compile time to ensure our file offset is at least 64 bits */
2020-01-22 13:10:59 +00:00
COMPILE_TIME_ASSERT(sizeof(options->infile_offset) >= 8);
#endif
2020-01-22 13:10:59 +00:00
has_output = grib_options_on("U");
has_input_extra = grib_options_on("I");
options->repack = grib_options_on("r");
options->gts = grib_options_on("g");
2014-01-10 14:07:02 +00:00
if (grib_options_on("d:")) {
2020-01-22 13:10:59 +00:00
char* endPtr = NULL; /* for error handling */
const char* optionStr = grib_options_get_option("d:");
2020-01-22 13:10:59 +00:00
options->constant = strtod(optionStr, &endPtr);
if (*endPtr) {
fprintf(stderr, "Invalid number for -d option: '%s'\n", optionStr);
exit(1);
}
2020-01-22 13:10:59 +00:00
options->repack = 1;
2013-03-25 12:04:10 +00:00
}
2020-01-22 13:10:59 +00:00
if (grib_options_on("G"))
grib_gribex_mode_on(context);
2014-01-10 14:07:02 +00:00
2020-01-22 13:10:59 +00:00
nfiles = argc - optind;
if (nfiles < (1 + has_output + has_input_extra) && !options->infile)
usage();
2014-01-10 14:07:02 +00:00
if (has_input_extra) {
2020-01-22 13:10:59 +00:00
options->infile_extra = (grib_tools_file*)calloc(1, sizeof(grib_tools_file));
options->infile_extra->name = argv[optind];
2014-01-10 14:07:02 +00:00
}
if (!options->infile) {
2020-01-22 13:10:59 +00:00
for (i = optind + has_input_extra; i < argc - has_output; i++) {
grib_tools_file* p = NULL;
grib_tools_file* infile = (grib_tools_file*)calloc(1, sizeof(grib_tools_file));
infile->name = argv[i];
if (!options->infile)
options->infile = infile;
2014-01-10 14:07:02 +00:00
else {
2020-01-22 13:10:59 +00:00
p = options->infile;
while (p->next)
p = p->next;
p->next = infile;
2014-01-10 14:07:02 +00:00
}
}
}
if (has_output) {
2020-01-22 13:10:59 +00:00
options->outfile = (grib_tools_file*)calloc(1, sizeof(grib_tools_file));
options->outfile->name = strdup(argv[argc - 1]);
2014-01-10 14:07:02 +00:00
}
if (grib_options_on("o:")) {
2020-01-22 13:10:59 +00:00
options->outfile = (grib_tools_file*)calloc(1, sizeof(grib_tools_file));
options->outfile->name = grib_options_get_option("o:");
2014-01-10 14:07:02 +00:00
}
2013-03-25 12:04:10 +00:00
2020-01-22 13:10:59 +00:00
options->print_number = grib_options_on("N");
options->print_header = grib_options_on("H");
options->verbose = grib_options_on("v");
2014-01-10 14:07:02 +00:00
if (grib_options_on("q") && grib_options_command_line("q"))
2020-01-22 13:10:59 +00:00
options->verbose = 0;
2014-01-10 14:07:02 +00:00
2020-01-22 13:10:59 +00:00
options->fail = !grib_options_on("f");
2014-01-10 14:07:02 +00:00
if (grib_options_get_option("W:"))
2020-01-22 13:10:59 +00:00
options->default_print_width = atoi(grib_options_get_option("W:"));
2014-01-10 14:07:02 +00:00
if (grib_options_on("n:"))
2020-01-22 13:10:59 +00:00
options->name_space = grib_options_get_option("n:");
2014-01-10 14:07:02 +00:00
if (grib_options_on("m"))
2020-01-22 13:10:59 +00:00
options->name_space = strdup("mars");
2014-01-10 14:07:02 +00:00
2020-01-22 13:10:59 +00:00
if (grib_options_on("P:"))
karg = grib_options_get_option("P:");
2014-01-10 14:07:02 +00:00
else if (grib_options_on("p:")) {
2020-01-22 13:10:59 +00:00
karg = grib_options_get_option("p:");
options->name_space = NULL;
2014-01-10 14:07:02 +00:00
}
2020-01-22 13:10:59 +00:00
options->requested_print_keys_count = MAX_KEYS;
ret = parse_keyval_string(grib_tool_name, karg, 0, GRIB_TYPE_UNDEFINED,
options->requested_print_keys, &(options->requested_print_keys_count));
if (ret == GRIB_INVALID_ARGUMENT)
usage();
GRIB_CHECK_NOLINE(ret, 0);
2014-01-10 14:07:02 +00:00
2020-01-22 13:10:59 +00:00
options->strict = grib_options_on("S");
2014-01-10 14:07:02 +00:00
2020-01-22 13:10:59 +00:00
if (grib_options_on("M"))
grib_multi_support_off(context);
else
grib_multi_support_on(context);
2014-01-10 14:07:02 +00:00
2020-01-22 13:10:59 +00:00
if (grib_options_on("g"))
grib_gts_header_on(context);
else
grib_gts_header_off(context);
2014-01-10 14:07:02 +00:00
if (grib_options_on("V")) {
printf("\necCodes Version ");
2014-01-10 14:07:02 +00:00
grib_print_api_version(stdout);
printf("\n\n");
}
2013-03-25 12:04:10 +00:00
2014-01-10 14:07:02 +00:00
if (grib_options_on("s:")) {
2020-01-22 13:10:59 +00:00
sarg = grib_options_get_option("s:");
options->set_values_count = MAX_KEYS;
ret = parse_keyval_string(grib_tool_name, sarg, 1, GRIB_TYPE_UNDEFINED, options->set_values, &(options->set_values_count));
if (ret == GRIB_INVALID_ARGUMENT)
usage();
2014-01-10 14:07:02 +00:00
}
if (grib_options_on("b:")) {
2020-01-22 13:10:59 +00:00
barg = grib_options_get_option("b:");
options->set_values_count = MAX_KEYS;
ret = parse_keyval_string(grib_tool_name, barg, 0, GRIB_TYPE_STRING, options->set_values, &(options->set_values_count));
if (ret == GRIB_INVALID_ARGUMENT)
usage();
2014-01-10 14:07:02 +00:00
}
if (grib_options_on("c:")) {
2020-01-22 13:10:59 +00:00
sarg = grib_options_get_option("c:");
options->compare_count = MAX_KEYS;
ret = parse_keyval_string(grib_tool_name, sarg, 0, GRIB_TYPE_UNDEFINED, options->compare,
&(options->compare_count));
if (ret == GRIB_INVALID_ARGUMENT)
usage();
2014-01-10 14:07:02 +00:00
}
if (grib_options_on("e")) {
2020-01-22 13:10:59 +00:00
for (i = 0; i < names_count; i++) {
options->compare[i + options->compare_count].name = names[i];
options->compare[i + options->compare_count].type = GRIB_NAMESPACE;
2014-01-10 14:07:02 +00:00
}
2020-01-22 13:10:59 +00:00
options->compare_count += names_count;
2013-03-25 12:04:10 +00:00
}
2020-01-22 13:10:59 +00:00
warg = grib_options_get_option("w:");
2013-03-25 12:04:10 +00:00
2020-01-22 13:10:59 +00:00
options->constraints_count = MAX_KEYS;
ret = parse_keyval_string(grib_tool_name, warg, 1, GRIB_TYPE_UNDEFINED,
options->constraints, &(options->constraints_count));
if (ret == GRIB_INVALID_ARGUMENT)
usage();
2013-03-25 12:04:10 +00:00
2014-01-10 14:07:02 +00:00
return GRIB_SUCCESS;
2013-03-25 12:04:10 +00:00
}
char* grib_options_get_help(char* id)
{
2020-01-22 13:10:59 +00:00
int i = 0;
char msg[] = "ERROR: help not found for option ";
char* err = (char*)calloc(1, sizeof(msg) + 3);
sprintf(err, "%s%c\n", msg, *id);
for (i = 0; i < grib_options_count; i++) {
if (!strcmp(id, grib_options[i].id)) {
if (grib_options[i].help != NULL)
return grib_options[i].help;
else
break;
2014-01-10 14:07:02 +00:00
}
}
2020-01-22 13:10:59 +00:00
for (i = 0; i < grib_options_help_count; i++) {
if (!strcmp(id, grib_options_help_list[i].id)) {
2018-08-07 12:50:41 +00:00
return grib_options_help_list[i].help != NULL ? (char*)grib_options_help_list[i].help : err;
2014-01-10 14:07:02 +00:00
}
}
return err;
2013-03-25 12:04:10 +00:00
}
char* grib_options_get_args(char* id)
{
2020-01-22 13:10:59 +00:00
int i = 0;
char empty[] = "";
char msg[] = "ERROR: help not found for option -";
char* err = NULL;
if (id[1] != ':')
return strdup(empty);
err = (char*)calloc(1, sizeof(msg) + 3);
sprintf(err, "%s%c\n", msg, *id);
for (i = 0; i < grib_options_count; i++) {
if (!strcmp(id, grib_options[i].id)) {
2020-03-19 16:01:10 +00:00
if (grib_options[i].args != NULL) {
free(err);
2020-01-22 13:10:59 +00:00
return grib_options[i].args;
2020-03-19 16:01:10 +00:00
} else {
2020-01-22 13:10:59 +00:00
break;
2020-03-19 16:01:10 +00:00
}
2014-01-10 14:07:02 +00:00
}
}
2020-01-22 13:10:59 +00:00
for (i = 0; i < grib_options_help_count; i++) {
if (!strcmp(id, grib_options_help_list[i].id)) {
2020-03-20 12:43:03 +00:00
if (grib_options_help_list[i].args != NULL) {
free(err);
return (char*)grib_options_help_list[i].args;
} else {
return err;
}
2014-01-10 14:07:02 +00:00
}
}
return err;
2013-03-25 12:04:10 +00:00
}
2018-03-12 18:20:27 +00:00
void usage_doxygen(void)
{
2020-01-22 13:10:59 +00:00
int i = 0;
printf("/*! \\page %s %s\n", grib_tool_name, grib_tool_name);
printf("\\section DESCRIPTION \n %s\n\n", grib_tool_description);
printf("\\section USAGE \n %s \n %s\n\n", grib_tool_name, grib_tool_usage);
2014-01-10 14:07:02 +00:00
printf("\\section OPTIONS \n");
2020-01-22 13:10:59 +00:00
for (i = 0; i < grib_options_count; i++) {
2014-01-10 14:07:02 +00:00
if (grib_options[i].command_line) {
printf("-%c %s \\n",
2020-01-22 13:10:59 +00:00
grib_options[i].id[0],
grib_options_get_args(grib_options[i].id));
2014-01-10 14:07:02 +00:00
printf(" %s \\n \\n ",
2020-01-22 13:10:59 +00:00
grib_options_get_help(grib_options[i].id));
2014-01-10 14:07:02 +00:00
}
2013-03-25 12:04:10 +00:00
}
2014-01-10 14:07:02 +00:00
exit(1);
2013-03-25 12:04:10 +00:00
}
#if 0
2018-03-12 18:20:27 +00:00
void usage_doxygen(void) {
2014-01-10 14:07:02 +00:00
int i=0;
printf("/*! \\page %s %s\n",grib_tool_name,grib_tool_name);
printf("\\section DESCRIPTION \n%s\n\n",grib_tool_description);
printf("\\section USAGE \n%s \n%s\n\n",grib_tool_name,grib_tool_usage);
printf("\\section OPTIONS\n");
printf("<table frame=void border=0>\n");
for (i=0;i<grib_options_count;i++) {
if (grib_options[i].command_line) {
printf("<tr>\n");
printf("<td colspan=2>-%c %s</td>\n",
grib_options[i].id[0],
grib_options_get_args(grib_options[i].id));
printf("</tr><tr>\n");
printf("<td width=20></td><td>%s</td>",
grib_options_get_help(grib_options[i].id));
printf("</tr><tr><td></td></tr>\n");
}
}
printf("</table>\n");
exit(1);
2013-03-25 12:04:10 +00:00
}
#endif