1 /*--------------------------------------------------------------------
2  *
3  *	Copyright (c) 1991-2021 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
4  *	See LICENSE.TXT file for copying and redistribution conditions.
5  *
6  *	This program is free software; you can redistribute it and/or modify
7  *	it under the terms of the GNU Lesser General Public License as published by
8  *	the Free Software Foundation; version 3 or any later version.
9  *
10  *	This program is distributed in the hope that it will be useful,
11  *	but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *	GNU Lesser General Public License for more details.
14  *
15  *	Contact info: www.generic-mapping-tools.org
16  *--------------------------------------------------------------------*/
17 /*
18  * API functions to support the gmtconvert application.
19  *
20  * Author:	Paul Wessel
21  * Date:	1-JAN-2010
22  * Version:	6 API
23  *
24  * Brief synopsis: Read one or more data tables and can concatenated them
25  * vertically [Default] or horizontally (pasting), select certain columns,
26  * report only first and/or last record per segment, only print segment
27  * headers, and only report segments that passes a segment header search.
28  */
29 
30 #include "gmt_dev.h"
31 
32 #define THIS_MODULE_CLASSIC_NAME	"gmtconvert"
33 #define THIS_MODULE_MODERN_NAME	"gmtconvert"
34 #define THIS_MODULE_LIB		"core"
35 #define THIS_MODULE_PURPOSE	"Convert, paste, or extract columns from data tables"
36 #define THIS_MODULE_KEYS	"<D{,>D}"
37 #define THIS_MODULE_NEEDS	""
38 #define THIS_MODULE_OPTIONS "-:>Vabdefghioqsw" GMT_OPT("HMm")
39 
40 #define INV_ROWS	1
41 #define INV_SEGS	2
42 #define INV_TBLS	4
43 
44 #define EXCLUDE_HEADERS		0
45 #define EXCLUDE_DUPLICATES	1
46 
47 /* Control structure for gmtconvert */
48 
49 struct GMTCONVERT_CTRL {
50 	struct GMTCONVERT_Out {	/* -> */
51 		bool active;
52 		char *file;
53 	} Out;
54 	struct GMTCONVERT_A {	/* -A */
55 		bool active;
56 	} A;
57 	struct GMTCONVERT_C {	/* -C[+l<min>+u<max>+i>] */
58 		bool active, invert;
59 		uint64_t min, max;
60 	} C;
61 	struct GMTCONVERT_D {	/* -D[<template>][+o<orig>] */
62 		bool active;
63 		bool origin;
64 		unsigned int mode;
65 		unsigned int t_orig, s_orig;
66 		char *name;
67 	} D;
68 	struct GMTCONVERT_E {	/* -E */
69 		bool active;
70 		bool end;
71 		int mode;	/* -3, -1, -1, 0, or increment stride */
72 	} E;
73 	struct GMTCONVERT_F {	/* -F<mode> */
74 		bool active;
75 		struct GMT_SEGMENTIZE S;
76 	} F;
77 	struct GMTCONVERT_I {	/* -I[ast] */
78 		bool active;
79 		unsigned int mode;
80 	} I;
81 	struct GMTCONVERT_L {	/* -L */
82 		bool active;
83 	} L;
84 	struct GMTCONVERT_N {	/* -N<col>[+a|d] sorting */
85 		bool active;
86 		int dir;	/* +1 ascending [default], -1 descending */
87 		uint64_t col;
88 	} N;
89 	struct GMTCONVERT_Q {	/* -Q<selections> */
90 		bool active;
91 		struct GMT_INT_SELECTION *select;
92 	} Q;
93 	struct GMTCONVERT_S {	/* -S[~]\"search string\" */
94 		bool active;
95 		struct GMT_TEXT_SELECTION *select;
96 	} S;
97 	struct GMTCONVERT_T {	/* -T[h][d[[~]selection]] */
98 		bool active[2];
99 		bool text;
100 		struct GMT_INT_SELECTION *C;
101 	} T;
102 	struct GMTCONVERT_W {	/* -W[+n] */
103 		bool active;
104 		unsigned int mode;
105 	} W;
106 	struct GMTCONVERT_Z {	/* -Z[<first>]:[<last>] [DEPRECATED - use -q instead]*/
107 		bool active;
108 		int64_t first, last;
109 	} Z;
110 };
111 
New_Ctrl(struct GMT_CTRL * GMT)112 static void *New_Ctrl (struct GMT_CTRL *GMT) {	/* Allocate and initialize a new control structure */
113 	struct GMTCONVERT_CTRL *C;
114 
115 	C = gmt_M_memory (GMT, NULL, 1, struct GMTCONVERT_CTRL);
116 
117 	/* Initialize values whose defaults are not 0/false/NULL */
118 	C->C.max = ULONG_MAX;	/* Max records possible in one segment */
119 
120 	return (C);
121 }
122 
Free_Ctrl(struct GMT_CTRL * GMT,struct GMTCONVERT_CTRL * C)123 static void Free_Ctrl (struct GMT_CTRL *GMT, struct GMTCONVERT_CTRL *C) {	/* Deallocate control structure */
124 	if (!C) return;
125 	gmt_M_str_free (C->Out.file);
126 	gmt_M_str_free (C->D.name);
127 	if (C->S.active) gmt_free_text_selection (GMT, &C->S.select);
128 	if (C->Q.active) gmt_free_int_selection (GMT, &C->Q.select);
129 	if (C->T.active[EXCLUDE_DUPLICATES]) gmt_free_int_selection (GMT, &C->T.C);
130 	gmt_M_free (GMT, C);
131 }
132 
usage(struct GMTAPI_CTRL * API,int level)133 static int usage (struct GMTAPI_CTRL *API, int level) {
134 	const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE);
135 	if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR);
136 	GMT_Usage (API, 0, "usage: %s [<table>] [-A] [-C[+l<min>][+u<max>][+i]] [-D[<template>[+o<orig>]]] "
137 		"[-E[f|l|m|M<stride>]] [-F%s] [-I[tsr]] [-L] [-N<col>[+a|d]] [-Q[~]<selection>] [-S[~]\"search string\"] "
138 		"[-T[h][d[[~]<selection>]]] [%s] [-W[+n]] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s] [%s]\n",
139 		name, GMT_SEGMENTIZE4, GMT_V_OPT, GMT_a_OPT, GMT_b_OPT, GMT_d_OPT, GMT_e_OPT, GMT_f_OPT, GMT_g_OPT,
140 		GMT_h_OPT, GMT_i_OPT, GMT_o_OPT, GMT_q_OPT, GMT_s_OPT, GMT_w_OPT, GMT_colon_OPT, GMT_PAR_OPT);
141 
142 	if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS);
143 
144 	GMT_Message (API, GMT_TIME_NONE, "  REQUIRED ARGUMENTS:\n");
145 	GMT_Option (API, "<");
146 	GMT_Message (API, GMT_TIME_NONE, "\n  OPTIONAL ARGUMENTS:\n");
147 	GMT_Usage (API, 1, "\n-A Paste files horizontally, not concatenate vertically [Default]. "
148 		"All files must have the same number of segments and rows, "
149 		"but they may differ in their number of columns.");
150 	GMT_Usage (API, 1, "\n-C[+l<min>][+u<max>][+i]");
151 	GMT_Usage (API, -2, "Only output segments whose number of records matches criteria:");
152 	GMT_Usage (API, 3, "+l Segment must have at least <min> records [0].");
153 	GMT_Usage (API, 3, "+u Segment must have at most <max> records [inf].");
154 	GMT_Usage (API, 3, "+i Invert the test.");
155 	GMT_Usage (API, 1, "\n-D[<template>[+o<orig>]]");
156 	GMT_Usage (API, -2, "Write individual segments to separate files [Default writes one "
157 		"multisegment file to standard output].  Append file name template which MUST "
158 		"contain a C-style format for an integer (e.g., %%d) that represents "
159 		"a sequential segment number across all tables (if more than one table) "
160 		"[Default uses gmtconvert_segment_%%d.txt (or .bin for binary)]. "
161 		"Use +o<orig> to start numbering at <orig> [0]. "
162 		"Alternatively, supply a template with two long formats and we will "
163 		"replace them with the table number and table segment numbers. "
164 		"Use +o<t_orig>/<s_orig> to start numbering at <t_orig> for tables and <s_orig> for segments [0/0].");
165 	GMT_Usage (API, 1, "\n-E[f|l|m|M<stride>]");
166 	GMT_Usage (API, -2, "Extract first and last record per segment only [Output all records]. Optional directives:");
167 	GMT_Usage (API, 3, "f: Output first record only.");
168 	GMT_Usage (API, 3, "l: Output last record only.");
169 	GMT_Usage (API, 3, "m: Output every <stride> records.");
170 	GMT_Usage (API, 3, "M: Same as m but always includes the last record.");
171 	gmt_segmentize_syntax (API->GMT, 'F', 0);
172 	GMT_Usage (API, 1, "\n-I[tsr]");
173 	GMT_Usage (API, -2, "Invert output order of (t)ables, (s)egments, or (r)ecords.  Append any combination of directives:");
174 	GMT_Usage (API, 3, "t: Reverse the order of input tables on output.");
175 	GMT_Usage (API, 3, "s: Reverse the order of segments within each table on output.");
176 	GMT_Usage (API, 3, "r: Reverse the order of records within each segment on output [Default].");
177 	GMT_Usage (API, 1, "\n-L Output only segment headers and skip all data records. "
178 		"Requires ASCII input data [Output headers and data].");
179 	GMT_Usage (API, 1, "\n-N<col>[+a|d]");
180 	GMT_Usage (API, -2, "Numerically sort all records per segment based on data in column <col>:");
181 	GMT_Usage (API, 3, "+a Sort into ascending order [Default].");
182 	GMT_Usage (API, 3, "+d Sort into descending order.");
183 	GMT_Usage (API, 1, "\n-Q[~]<selection>");
184 	GMT_Usage (API, -2, "Only output specified segment numbers in <selection> [All]. "
185 		"<selection> syntax is [~]<range>[,<range>,...] where each <range> of items is "
186 		"either a single number, start-stop (for range), start:step:stop (for stepped range), "
187 		"or +f<file> for a file list with one <range> selection per line. "
188 		"A leading ~ will invert the selection and write all segments but the ones listed.");
189 	GMT_Usage (API, 1, "\n-S[~]\"search string\"");
190 	GMT_Usage (API, -2, "Only output segments whose headers contain the pattern \"string\". "
191 		"Use -S~\"string\" to output segment that DO NOT contain this pattern. "
192 		"If your pattern begins with ~, escape it with \\~. "
193 		"To match OGR aspatial values, use name=value, and to match headers against "
194 		"extended regular expressions use -S[~]/regexp/[i] (i for case-insensitive). "
195 		"Give +f<file> for a file list with such patterns, one per line. "
196 		"To give a single pattern starting with +f, escape it with \\+f.");
197 	GMT_Usage (API, 1, "\n-T[h][d[[~]<selection>]]");
198 	GMT_Usage (API, -2, "Skip certain types of records.  Append one or both of these directives:");
199 	GMT_Usage (API, 3, "h: Prevent the writing of segment headers [Default].");
200 	GMT_Usage (API, 3, "d: Prevent the writing of duplicate data records.");
201 	GMT_Usage (API, -2, "Optionally, append selection of columns to consider in the test [all]. "
202 		"<selection> syntax is [~]<range>[,<range>,...] where each <range> of items is "
203 		"either a single number, start-stop (for range), start:step:stop (for stepped range). "
204 		"To include trailing text in the comparison, add column t.  If no numerical columns "
205 		"are specified, only t, then we only use trailing text comparisons to decide.");
206 	GMT_Option (API, "V");
207 	GMT_Usage (API, 1, "\n-W[+n]");
208 	GMT_Usage (API, -2, "Convert trailing text to numbers, if possible.  Append +n to suppress NaN columns.");
209 	GMT_Option (API, "a,bi,bo,d,e,f,g,h,i,o,q,s,w,:,.");
210 
211 	return (GMT_MODULE_USAGE);
212 }
213 
parse(struct GMT_CTRL * GMT,struct GMTCONVERT_CTRL * Ctrl,struct GMT_OPTION * options)214 static int parse (struct GMT_CTRL *GMT, struct GMTCONVERT_CTRL *Ctrl, struct GMT_OPTION *options) {
215 	/* This parses the options provided to gmtconvert and sets parameters in CTRL.
216 	 * Any GMT common options will override values set previously by other commands.
217 	 * It also replaces any file names specified as input or output with the data ID
218 	 * returned when registering these sources/destinations with the API.
219 	 */
220 
221 	unsigned int pos, n_errors = 0, k, n_files = 0;
222 	int n = 0;
223 	int64_t value = 0;
224 	char p[GMT_BUFSIZ] = {""}, *c = NULL;
225 	struct GMT_OPTION *opt = NULL;
226 	struct GMTAPI_CTRL *API = GMT->parent;
227 
228 	for (opt = options; opt; opt = opt->next) {
229 		switch (opt->option) {
230 
231 			case '<':	/* Skip input files */
232 				if (GMT_Get_FilePath (API, GMT_IS_DATASET, GMT_IN, GMT_FILE_REMOTE, &(opt->arg))) n_errors++;;
233 				break;
234 			case '>':	/* Got named output file */
235 				if (n_files++ > 0) { n_errors++; continue; }
236 				Ctrl->Out.active = true;
237 				if (opt->arg[0]) Ctrl->Out.file = strdup (opt->arg);
238 				if (GMT_Get_FilePath (API, GMT_IS_DATASET, GMT_OUT, GMT_FILE_LOCAL, &(Ctrl->Out.file))) n_errors++;
239 				break;
240 
241 			/* Processes program-specific parameters */
242 
243 			case 'A':	/* pAste mode */
244 				n_errors += gmt_M_repeated_module_option (API, Ctrl->A.active);
245 				Ctrl->A.active = true;
246 				break;
247 			case 'C':	/* record-count selection mode */
248 				n_errors += gmt_M_repeated_module_option (API, Ctrl->C.active);
249 				Ctrl->C.active = true;
250 				pos = 0;
251 				while (gmt_getmodopt (GMT, 'C', opt->arg, "ilu", &pos, p, &n_errors) && n_errors == 0) {	/* Looking for +i, +l, +u */
252 					switch (p[0]) {
253 						case 'i':	/* Invert selection */
254 							Ctrl->C.invert = true;	break;
255 						case 'l':	/* Set fewest records required */
256 						 	if ((value = atol (&p[1])) < 0)
257 								GMT_Report (API, GMT_MSG_ERROR, "The -C+l modifier was given negative record count!\n");
258 							else
259 								Ctrl->C.min = (uint64_t)value;
260 							break;
261 						case 'u':	/* Set max records required */
262 					 		if ((value = atol (&p[1])) < 0)
263 								GMT_Report (API, GMT_MSG_ERROR, "The -C+u modifier was given negative record count!\n");
264 							else
265 								Ctrl->C.max = (uint64_t)value;
266 							break;
267 						default:	/* These are caught in gmt_getmodopt so break is just for Coverity */
268 							break;
269 					}
270 				}
271 				break;
272 			case 'D':	/* Write each segment to a separate output file */
273 				n_errors += gmt_M_repeated_module_option (API, Ctrl->D.active);
274 				Ctrl->D.active = true;
275 				if ((c = strstr (opt->arg, "+o"))) {	/* Gave new origins for tables and segments (or just segments) */
276 					n = sscanf (&c[2], "%d/%d", &Ctrl->D.t_orig, &Ctrl->D.s_orig);
277 					if (n == 1) Ctrl->D.s_orig = Ctrl->D.t_orig, Ctrl->D.t_orig = 0;
278 					c[0] = '\0';	/* Chop off modifier */
279 					Ctrl->D.origin = true;
280 				}
281 				if (*opt->arg) /* optarg is optional */
282 					Ctrl->D.name = strdup (opt->arg);
283 				if (c) c[0] = '+';	/* Restore modifier */
284 				break;
285 			case 'E':	/* Extract ends only */
286 				n_errors += gmt_M_repeated_module_option (API, Ctrl->E.active);
287 				Ctrl->E.active = true;
288 				switch (opt->arg[0]) {
289 					case 'f':		/* Get first point only */
290 						Ctrl->E.mode = -1; break;
291 					case 'l':		/* Get last point only */
292 						Ctrl->E.mode = -2; break;
293 					case 'M':		/* Set modulo step */
294 						Ctrl->E.end = true;	/* Include last point */
295 						/* Intentionally fall through - to set the step */
296 					case 'm':		/* Set modulo step */
297 						Ctrl->E.mode = atoi (&opt->arg[1]); break;
298 					default:		/* Get first and last point only */
299 						Ctrl->E.mode = -3; break;
300 				}
301 				break;
302 			case 'F':
303 				n_errors += gmt_M_repeated_module_option (API, Ctrl->F.active);
304 				Ctrl->F.active = true;
305 				if (opt->arg[0] == '\0') {	/* No arguments, must be old GMT4 option -F */
306 					if (gmt_M_compat_check (GMT, 4)) {
307 						GMT_Report (API, GMT_MSG_COMPAT,
308 						            "Option -F for output columns is deprecated; use -o instead\n");
309 						gmt_parse_o_option (GMT, opt->arg);
310 					}
311 					else
312 						n_errors += gmt_default_error (GMT, opt->option);
313 					break;
314 				}
315 				/* Modern options */
316 				n_errors += gmt_parse_segmentize (GMT, opt->option, opt->arg, 0, &(Ctrl->F.S));
317 				break;
318 			case 'I':	/* Invert order or tables, segments, rows as indicated */
319 				n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active);
320 				Ctrl->I.active = true;
321 				for (k = 0; opt->arg[k]; k++) {
322 					switch (opt->arg[k]) {
323 						case 't': Ctrl->I.mode |= INV_TBLS; break;	/* Reverse table order */
324 						case 's': Ctrl->I.mode |= INV_SEGS; break;	/* Reverse segment order */
325 						case 'r': Ctrl->I.mode |= INV_ROWS; break;	/* Reverse record order */
326 						default:
327 							GMT_Report (API, GMT_MSG_ERROR,
328 							            "The -I option does not recognize modifier %c\n", (int)opt->arg[k]);
329 							n_errors++;
330 							break;
331 					}
332 				}
333 				if (Ctrl->I.mode == 0) Ctrl->I.mode = INV_ROWS;	/* Default is -Ir */
334 				break;
335 			case 'L':	/* Only output segment headers */
336 				n_errors += gmt_M_repeated_module_option (API, Ctrl->L.active);
337 				Ctrl->L.active = true;
338 				break;
339 			case 'N':	/* Sort per segment on specified column */
340 				n_errors += gmt_M_repeated_module_option (API, Ctrl->N.active);
341 				Ctrl->N.active = true;
342 				if ((c = strstr (opt->arg, "+a")) || (c = strstr (opt->arg, "+d"))) {	/* New syntax */
343 					Ctrl->N.dir = (c[1] == 'd') ? -1 : +1;
344 					c[0] = '\0';	/* Chop off modifier */
345 				}
346 				value = atol (opt->arg);
347 				if (c)
348 					c[0] = '+';	/* Restore modifier */
349 				else
350 					Ctrl->N.dir = (value < 0 || opt->arg[0] == '-') ? -1 : +1;
351 				Ctrl->N.col = int64_abs (value);
352 				break;
353 			case 'Q':	/* Only report for specified segment numbers */
354 				n_errors += gmt_M_repeated_module_option (API, Ctrl->Q.active);
355 				Ctrl->Q.active = true;
356 				Ctrl->Q.select = gmt_set_int_selection (GMT, opt->arg);
357 				break;
358 			case 'S':	/* Segment header pattern search */
359 				n_errors += gmt_M_repeated_module_option (API, Ctrl->S.active);
360 				Ctrl->S.active = true;
361 				Ctrl->S.select = gmt_set_text_selection (GMT, opt->arg);
362 				break;
363 			case 'T':	/* -T[h]: Do not write segment headers, -Td: Skip duplicate records */
364 				strncpy (p, opt->arg, GMT_BUFSIZ-1);
365 				if ((c = strchr (p, 'd'))) { /* Skip duplicates */
366 					char *d = NULL;
367 					n_errors += gmt_M_repeated_module_option (API, Ctrl->T.active[EXCLUDE_DUPLICATES]);
368 					Ctrl->T.active[EXCLUDE_DUPLICATES] = true;
369 					if ((d = strstr (c, ",t")) || (d = strchr (c, 't'))) {	/* Got either d<cols>,t or just t */
370 						Ctrl->T.text = true;
371 						d[0] = '\0';
372 					}
373 					if (c[1]) Ctrl->T.C = gmt_set_int_selection (GMT, &c[1]);	/* If we gave -Tdt then no columns and c[1] is 0 */
374 				}
375 				if (!p[0] || strchr (p, 'h')) {	/* Skip segment headers */
376 					n_errors += gmt_M_repeated_module_option (API, Ctrl->T.active[EXCLUDE_HEADERS]);
377 					Ctrl->T.active[EXCLUDE_HEADERS] = true;
378 				}
379 				break;
380 			case 'W':
381 				n_errors += gmt_M_repeated_module_option (API, Ctrl->W.active);
382 				Ctrl->W.active = true;
383 				if (!strncmp (opt->arg, "+n", 2U))
384 					Ctrl->W.mode = 1;
385 				break;
386 			case 'Z':
387 				n_errors += gmt_M_repeated_module_option (API, Ctrl->Z.active);
388 				Ctrl->Z.active = true;
389 				GMT_Report (API, GMT_MSG_COMPAT, "Option -Z is deprecated (but still works); Use common option -q instead\n");
390 				if ((c = strchr (opt->arg, ':')) || (c = strchr (opt->arg, '/'))) {	/* Got [<first>]:[<last>] or [<first>]/[<last>] */
391 					char div = c[0];	/* Either : or / */
392 					if (opt->arg[0] == div) /* No first given, default to 0 */
393 						Ctrl->Z.last = atol (&opt->arg[1]);
394 					else { /* Gave first, and maybe last */
395 						c[0] = '\0';	/* Chop off last */
396 						Ctrl->Z.first = atol (opt->arg);
397 						Ctrl->Z.last = (c[1]) ? (int64_t)atol (&c[1]) : INTMAX_MAX;	/* Last record if not given */
398 						c[0] = div;	/* Restore */
399 					}
400 				}
401 				else /* No colon means first is 0 and given value is last */
402 					Ctrl->Z.last = atol (opt->arg);
403 				/* Adjust to system where first record is 1 since we must increment n_in_rows before applying -Z check later */
404 				Ctrl->Z.first++;	if (Ctrl->Z.last < INTMAX_MAX) Ctrl->Z.last++;
405 				GMT_Report (API, GMT_MSG_DEBUG, "Output record numbers %" PRIu64 " through = %" PRIu64 "\n", Ctrl->Z.first, Ctrl->Z.last);
406 				break;
407 
408 			default:	/* Report bad options */
409 				n_errors += gmt_default_error (GMT, opt->option);
410 				break;
411 		}
412 	}
413 
414 	if (Ctrl->D.active) {	/* Validate the name template, if given */
415 		/* Must write individual segments to separate files so create the needed name template */
416 		unsigned int n_formats = 0;
417 		if (!Ctrl->D.name) {	/* None give, auto-assign to segment files with extension based on binary or not */
418 			Ctrl->D.name = GMT->common.b.active[GMT_OUT] ?
419 				strdup ("gmtconvert_segment_%d.bin") : strdup ("gmtconvert_segment_%d.txt");
420 			Ctrl->D.mode = GMT_WRITE_SEGMENT;
421 		}
422 		else { /* Ctrl->D.name given, need to check correct format */
423 			char *p = Ctrl->D.name;
424 			while ( (p = strchr (p, '%')) ) {
425 				/* found %, now check format */
426 				++p;	/* Skip the % sign */
427 				p += strspn (p, "0123456789."); /* span past digits and decimal */
428 				if ( strspn (p, "diu") == 0 ) {
429 					/* No valid conversion specifier */
430 					GMT_Report (API, GMT_MSG_ERROR,
431 						"Option -D: Use of unsupported conversion specifier at position %" PRIuS " in format string '%s'.\n",
432 						p - Ctrl->D.name + 1, Ctrl->D.name);
433 					n_errors++;
434 				}
435 				++n_formats;
436 			}
437 			if ( n_formats == 0 || n_formats > 2 ) {	/* Incorrect number of format specifiers */
438 				GMT_Report (API, GMT_MSG_ERROR,
439 					"Option -D: Incorrect number of format specifiers in format string '%s'.\n",
440 					Ctrl->D.name);
441 				n_errors++;
442 			}
443 			/* The io_mode tells the i/o function to split tables or segments into files, if requested */
444 			Ctrl->D.mode = (n_formats == 2) ? GMT_WRITE_TABLE_SEGMENT: GMT_WRITE_SEGMENT;
445 		}
446 	}
447 	n_errors += gmt_M_check_condition (GMT, GMT->common.b.active[GMT_IN] && GMT->common.b.ncol[GMT_IN] == 0,
448 	                                 "Must specify number of columns in binary input data (-bi)\n");
449 	n_errors += gmt_M_check_condition (GMT, GMT->common.b.active[GMT_IN] && (Ctrl->L.active || Ctrl->S.active),
450 	                                 "Options -L or -S requires ASCII input data\n");
451 	n_errors += gmt_M_check_condition (GMT, Ctrl->C.active && (Ctrl->C.min > Ctrl->C.max),
452 	                                 "Option -C: minimum records cannot exceed maximum records\n");
453 	n_errors += gmt_M_check_condition (GMT, Ctrl->D.active && Ctrl->D.name && !strstr (Ctrl->D.name, "%"),
454 	                                 "Option -D: Output template must contain %%d\n");
455 	n_errors += gmt_M_check_condition (GMT, Ctrl->Q.active && Ctrl->S.active,
456 	                                 "Only one of -Q and -S can be used simultaneously\n");
457 	n_errors += gmt_M_check_condition (GMT, Ctrl->N.active && Ctrl->F.active,
458 	                                 "The -N option cannot be used with -F\n");
459 	n_errors += gmt_M_check_condition (GMT, Ctrl->Z.active && GMT->common.q.active[GMT_OUT],
460 	                                 "The deprecated -Z option cannot be used with -qo\n");
461 	n_errors += gmt_M_check_condition (GMT, n_files > 1, "Only one output destination can be specified\n");
462 
463 	return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR);
464 }
465 
gmtconvert_is_duplicate_row(struct GMT_DATASEGMENT * S,struct GMT_INT_SELECTION * C,bool text,uint64_t row)466 GMT_LOCAL bool gmtconvert_is_duplicate_row (struct GMT_DATASEGMENT *S, struct GMT_INT_SELECTION *C, bool text, uint64_t row) {
467 	uint64_t col, k;
468 	if (C == NULL) {
469 		/* Loop over all columns and compare the two records, if any differ then return false.
470 		 * If passes all columns then they are the same and we return true. */
471 		if (!text) {
472 			for (col = 0; col < S->n_columns; col++)
473 				if (!doubleAlmostEqualZero (S->data[col][row], S->data[col][row-1])) return false;
474 		}
475 	}
476 	else if (C->invert) {	/* Only compare the columns not given in select */
477 		for (col = k = 0; col < S->n_columns; col++) {
478 			if (col == C->item[k]) {	/* Skip this guy */
479 				k++;
480 				continue;
481 			}
482 			if (!doubleAlmostEqualZero (S->data[col][row], S->data[col][row-1])) return false;
483 		}
484 	}
485 	else {	/* Only compare the columns given in select */
486 		for (k = 0; k < C->n; k++) {
487 			col = C->item[k];
488 			if (!doubleAlmostEqualZero (S->data[col][row], S->data[col][row-1])) return false;
489 		}
490 	}
491 	if (text && S->text && S->text[row] && S->text[row-1]) return (!strcmp (S->text[row], S->text[row-1]));	/* Also compare trailing text */
492 	return true;
493 }
494 
495 /* Must free allocated memory before returning */
496 #define bailout(code) {gmt_M_free_options (mode); return (code);}
497 #define Return(code) {Free_Ctrl (GMT, Ctrl); gmt_end_module (GMT, GMT_cpy); bailout (code);}
498 
GMT_gmtconvert(void * V_API,int mode,void * args)499 EXTERN_MSC int GMT_gmtconvert (void *V_API, int mode, void *args) {
500 	bool match = false, prevent_seg_headers = false;
501 	int error = 0;
502 	uint64_t out_col, col, n_cols_in = 0, n_cols_out, tbl, tlen, n_duplicates = 0;
503 	uint64_t n_horizontal_tbls, n_vertical_tbls, tbl_ver, tbl_hor, use_tbl;
504 	uint64_t last_row, n_rows, row, seg, n_out_seg = 0, out_seg = 0;
505 	int64_t n_in_rows;
506 
507 	double *val = NULL;
508 
509 	char *method[2] = {"concatenated", "pasted"}, *p = NULL;
510 
511 	struct GMTCONVERT_CTRL *Ctrl = NULL;
512 	struct GMT_DATASET *D[2] = {NULL, NULL};	/* Pointer to GMT multisegment table(s) in and out */
513 	struct GMT_DATASEGMENT *S = NULL, *Si = NULL, *So = NULL;
514 	struct GMT_DATASET_HIDDEN *DHi = NULL, *DHo = NULL;
515 	struct GMT_DATATABLE_HIDDEN *THi = NULL, *THo = NULL;
516 	struct GMT_DATASEGMENT_HIDDEN *SHi = NULL, *SHo = NULL;
517 	struct GMT_CTRL *GMT = NULL, *GMT_cpy = NULL;
518 	struct GMT_OPTION *options = NULL;
519 	struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API);	/* Cast from void to GMTAPI_CTRL pointer */
520 
521 	/*----------------------- Standard module initialization and parsing ----------------------*/
522 
523 	if (API == NULL) return (GMT_NOT_A_SESSION);
524 	if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE));	/* Return the purpose of program */
525 	options = GMT_Create_Options (API, mode, args);	if (API->error) return (API->error);	/* Set or get option list */
526 
527 	if ((error = gmt_report_usage (API, options, 1, usage)) != GMT_NOERROR) bailout (error);	/* Give usage if requested */
528 
529 	/* Parse the command-line arguments */
530 
531 	if ((GMT = gmt_init_module (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_KEYS, THIS_MODULE_NEEDS, NULL, &options, &GMT_cpy)) == NULL) bailout (API->error); /* Save current state */
532 	if (GMT_Parse_Common (API, THIS_MODULE_OPTIONS, options)) Return (API->error);
533 	Ctrl = New_Ctrl (GMT);	/* Allocate and initialize a new control structure */
534 	if ((error = parse (GMT, Ctrl, options)) != 0) Return (error);
535 
536 	/*---------------------------- This is the gmtconvert main code ----------------------------*/
537 
538 	GMT_Report (API, GMT_MSG_INFORMATION, "Processing input table data\n");
539 
540 	if (GMT_Init_IO (API, GMT_IS_DATASET, GMT_IS_POINT, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) {
541 		Return (API->error);	/* Establishes data files or stdin */
542 	}
543 
544 	/* Read in the input tables */
545 
546 	if ((D[GMT_IN] = GMT_Read_Data (API, GMT_IS_DATASET, GMT_IS_FILE, 0, GMT_READ_NORMAL, NULL, NULL, NULL)) == NULL) {
547 		Return (API->error);
548 	}
549 
550 	if (D[GMT_IN]->n_records == 0) {
551 		GMT_Report (API, GMT_MSG_WARNING, "No data records provided\n");
552 		Return (GMT_NOERROR);
553 	}
554 	if (GMT->common.a.active && D[GMT_IN]->n_tables > 1) {
555 		GMT_Report (API, GMT_MSG_ERROR, "The -a option requires a single table only.\n");
556 		Return (GMT_RUNTIME_ERROR);
557 	}
558 	DHi = gmt_get_DD_hidden (D[GMT_IN]);
559 	THi = gmt_get_DT_hidden (D[GMT_IN]->table[0]);
560 	if (GMT->common.a.active && THi->ogr) {
561 		GMT_Report (API, GMT_MSG_ERROR, "The -a option requires a single table without OGR metadata.\n");
562 		Return (GMT_RUNTIME_ERROR);
563 	}
564 
565 	if (Ctrl->T.active[EXCLUDE_HEADERS] && !gmt_M_file_is_memory (Ctrl->Out.file))
566 		prevent_seg_headers = true;
567 
568 	if (Ctrl->T.active[EXCLUDE_DUPLICATES] && Ctrl->T.C) {
569 		for (unsigned int k = 0; k <  Ctrl->T.C->n; k++) {
570 			if (Ctrl->T.C->item[k] >= D[GMT_IN]->n_columns) {
571 				GMT_Report (API, GMT_MSG_ERROR, "The -Td<cols> option has entries (%d) exceeding the number of data columns (%d).\n", (unsigned int)Ctrl->T.C->item[k], (unsigned int)D[GMT_IN]->n_columns);
572 				Return (GMT_RUNTIME_ERROR);
573 			}
574 		}
575 	}
576 
577 	if (prevent_seg_headers)	/* Turn off segment headers on file output */
578 		GMT->current.io.skip_headers_on_outout = true;
579 
580 	if (Ctrl->F.active) {	/* Segmentizing happens here and then we are done */
581 		D[GMT_OUT] = gmt_segmentize_data (GMT, D[GMT_IN], &(Ctrl->F.S));	/* Segmentize the data */
582 		if (GMT_Destroy_Data (API, &D[GMT_IN]) != GMT_NOERROR) {	/* Be gone with the original */
583 			Return (API->error);
584 		}
585 		if (D[GMT_OUT]->n_segments > 1) gmt_set_segmentheader (GMT, GMT_OUT, true);	/* Turn on segment headers on output */
586 
587 		DHo = gmt_get_DD_hidden (D[GMT_OUT]);
588 		if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, D[GMT_OUT]->geometry, DHo->io_mode, NULL, Ctrl->Out.file, D[GMT_OUT]) != GMT_NOERROR) {
589 			Return (API->error);
590 		}
591 		if (prevent_seg_headers) GMT->current.io.skip_headers_on_outout = false;	/* Restore to default if it was changed */
592 		Return (GMT_NOERROR);	/* We are done! */
593 	}
594 
595 	if (Ctrl->W.active) {	/* Text to data happens here and then we are done */
596 		double out[GMT_BUFSIZ], *tmp = NULL;
597 		struct GMT_RECORD *Out = NULL;
598 		char *nan = NULL;
599 		uint64_t n_col = 0, k;
600 		S = D[GMT_IN]->table[0]->segment[0];	/* Short-hand */
601 		if (S->text) {	/* Has trailing text */
602 			n_col = n_cols_in = GMT_Get_Values (API, S->text[0], out, GMT_BUFSIZ);
603 			if (Ctrl->W.mode) {	/* Only wants non-NaN columns from trailing text */
604 				nan = gmt_M_memory (GMT, NULL, n_col, char);
605 				tmp = gmt_M_memory (GMT, NULL, n_col, double);
606 				for (col = 0; col < n_col; col++) if (gmt_M_is_dnan (out[col])) nan[col] = 1, n_cols_in--;
607 				GMT_Report (API, GMT_MSG_INFORMATION, "First record trailing text converted to %" PRIu64 " columns, with %" PRIu64 " of them yielding NaNs [skipped]\n", n_col, n_col - n_cols_in);
608 			}
609 			else
610 				GMT_Report (API, GMT_MSG_INFORMATION, "First record trailing text converted to %" PRIu64 " columns\n", n_col);
611 		}
612 		else
613 			GMT_Report (API, GMT_MSG_WARNING, "No trialing text found in first record; -W will not have any effect.\n");
614 		if (GMT_Init_IO (API, GMT_IS_DATASET, GMT_IS_POINT, GMT_OUT, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) {	/* Registers default output destination, unless already set */
615 			if (Ctrl->W.mode) {gmt_M_free (GMT, nan); gmt_M_free (GMT, tmp);}
616 			Return (API->error);
617 		}
618 		if (GMT_Begin_IO (API, GMT_IS_DATASET, GMT_OUT, GMT_HEADER_ON) != GMT_NOERROR) {	/* Enables data output and sets access mode */
619 			if (Ctrl->W.mode) {gmt_M_free (GMT, nan); gmt_M_free (GMT, tmp);}
620 			Return (API->error);
621 		}
622 		if ((error = GMT_Set_Columns (API, GMT_OUT, (unsigned int)(n_cols_in + S->n_columns), GMT_COL_FIX_NO_TEXT)) != GMT_NOERROR) {
623 			if (Ctrl->W.mode) {gmt_M_free (GMT, nan); gmt_M_free (GMT, tmp);}
624 			Return (error);
625 		}
626 		Out = gmt_new_record (GMT, out, NULL);	/* Since we only need to worry about numerics in this module */
627 		for (tbl = 0; tbl < D[GMT_IN]->n_tables; tbl++) {
628 			for (seg = 0; seg < D[GMT_IN]->table[tbl]->n_segments; seg++) {	/* For each segment in the tables */
629 				S = D[GMT_IN]->table[tbl]->segment[seg];	/* Short-hand */
630 				if (D[GMT_IN]->n_segments > 1 || S->header) GMT_Put_Record (API, GMT_WRITE_SEGMENT_HEADER, S->header);
631 				for (row = 0; row < S->n_rows; row++) {
632 					for (col = 0; col < S->n_columns; col++)
633 						out[col] = S->data[col][row];
634 					if (S->text) {
635 						if (Ctrl->W.mode) {	/* Exclude NaN columns */
636 							n_cols_in = GMT_Get_Values (API, S->text[row], tmp, (int)n_col);
637 							for (col = k = 0; col < n_cols_in; col++) if (!nan[col]) out[S->n_columns+k] = tmp[col], k++;
638 						}
639 						else
640 							GMT_Get_Values (API, S->text[row], &out[S->n_columns], (int)n_col);
641 					}
642 					GMT_Put_Record (API, GMT_WRITE_DATA, Out);	/* Write this to output */
643 				}
644 			}
645 		}
646 		gmt_M_free (GMT, Out);
647 		if (Ctrl->W.mode) {
648 			gmt_M_free (GMT, nan);
649 			gmt_M_free (GMT, tmp);
650 		}
651 		if (GMT_End_IO (API, GMT_OUT, 0) != GMT_NOERROR) {	/* Disables further data output */
652 			Return (API->error);
653 		}
654 		Return (GMT_NOERROR);	/* We are done! */
655 	}
656 
657 	/* Determine number of input and output columns for the selected options.
658 	 * For -A, require all tables to have the same number of segments and records. */
659 
660 	for (tbl = n_cols_in = n_cols_out = 0; tbl < D[GMT_IN]->n_tables; tbl++) {
661 		if (Ctrl->A.active) {	/* All tables must be of the same vertical shape */
662 			if (tbl && D[GMT_IN]->table[tbl]->n_records  != D[GMT_IN]->table[tbl-1]->n_records)  error = true;
663 			if (tbl && D[GMT_IN]->table[tbl]->n_segments != D[GMT_IN]->table[tbl-1]->n_segments) error = true;
664 		}
665 		n_cols_in += D[GMT_IN]->table[tbl]->n_columns;				/* This is the case for -A */
666 		n_cols_out = MAX (n_cols_out, D[GMT_IN]->table[tbl]->n_columns);	/* The widest table encountered */
667 	}
668 	n_cols_out = (Ctrl->A.active) ? n_cols_in : n_cols_out;	/* Default or Reset since we did not have -A */
669 
670 	if (error) {
671 		GMT_Report (API, GMT_MSG_ERROR, "Parsing requires files with same number of records.\n");
672 		Return (GMT_RUNTIME_ERROR);
673 	}
674 	if (n_cols_out == 0 && !GMT->current.io.trailing_text[GMT_OUT]) {
675 		GMT_Report (API, GMT_MSG_ERROR, "Selection led to no output columns.\n");
676 		Return (GMT_RUNTIME_ERROR);
677 
678 	}
679 	if ((error = GMT_Set_Columns (API, GMT_OUT, (unsigned int)n_cols_out, (D[GMT_IN]->type == GMT_READ_DATA) ? GMT_COL_FIX_NO_TEXT : GMT_COL_FIX)) != GMT_NOERROR) {
680 		Return (error);
681 	}
682 
683 	if (Ctrl->S.active && GMT->current.io.ogr == GMT_OGR_TRUE && (p = strchr (Ctrl->S.select->pattern[0], '=')) != NULL) {	/* Want to search for an aspatial value */
684 		*p = 0;	/* Skip the = sign */
685 		if ((Ctrl->S.select->ogr_item = gmt_get_ogr_id (GMT->current.io.OGR, Ctrl->S.select->pattern[0])) != GMT_NOTSET) {
686 			Ctrl->S.select->ogr_match = true;
687 			p++;
688 			strcpy (Ctrl->S.select->pattern[0], p);	/* Move the value over to the start */
689 		}
690 	}
691 
692 	/* We now know the exact number of segments and columns and an upper limit on total records.
693 	 * Allocate data set with a single table with those proportions. This copies headers as well */
694 
695 	DHi->dim[GMT_COL] = n_cols_out;	/* State we want a different set of columns on output */
696 	D[GMT_OUT] = GMT_Duplicate_Data (API, GMT_IS_DATASET, GMT_DUPLICATE_ALLOC + ((Ctrl->A.active) ? GMT_ALLOC_HORIZONTAL : GMT_ALLOC_NORMAL), D[GMT_IN]);
697 	DHo = gmt_get_DD_hidden (D[GMT_OUT]);
698 
699 	n_horizontal_tbls = (Ctrl->A.active) ? D[GMT_IN]->n_tables : 1;	/* Only with pasting do we go horizontally */
700 	n_vertical_tbls   = (Ctrl->A.active) ? 1 : D[GMT_IN]->n_tables;	/* Only for concatenation do we go vertically */
701 	val = gmt_M_memory (GMT, NULL, n_cols_in, double);
702 
703 	for (tbl_ver = 0; tbl_ver < n_vertical_tbls; tbl_ver++) {	/* Number of tables to place vertically */
704 		D[GMT_OUT]->table[tbl_ver]->n_records = 0;	/* Reset record count per table since we may return fewer than the original */
705 		THo = gmt_get_DT_hidden (D[GMT_OUT]->table[tbl_ver]);
706 		for (seg = 0; seg < D[GMT_IN]->table[tbl_ver]->n_segments; seg++) {	/* For each segment in the tables */
707 			S  = D[GMT_IN]->table[tbl_ver]->segment[seg];	/* Current input segment */
708 			So = D[GMT_OUT]->table[tbl_ver]->segment[seg];	/* Current output segment */
709 			SHi = gmt_get_DS_hidden (S);
710 			SHo = gmt_get_DS_hidden (So);
711 			if (Ctrl->L.active) SHo->mode = GMT_WRITE_HEADER;	/* Only write segment header */
712 			if (Ctrl->S.active) {		/* See if the combined segment header has text matching our search string */
713 				match = gmt_get_segtext_selection (GMT, Ctrl->S.select, S, match);
714 				if (Ctrl->S.select->invert == match) SHo->mode = GMT_WRITE_SKIP;	/* Mark segment to be skipped */
715 			}
716 			if (Ctrl->Q.active && !gmt_get_int_selection (GMT, Ctrl->Q.select, seg)) SHo->mode = GMT_WRITE_SKIP;	/* Mark segment to be skipped */
717 			if (Ctrl->C.active) {	/* See if the number of records in this segment passes our test for output */
718 				match = (S->n_rows >= Ctrl->C.min && S->n_rows <= Ctrl->C.max);
719 				if (Ctrl->C.invert == match) SHo->mode = GMT_WRITE_SKIP;	/* Mark segment to be skipped */
720 			}
721 			if (SHo->mode) continue;	/* No point copying values given segment content will be skipped */
722 			n_out_seg++;	/* Number of segments that passed the test */
723 			last_row = S->n_rows - 1;
724 			for (row = n_rows = 0, n_in_rows = 0; row <= last_row; row++) {	/* Go down all the rows */
725 				n_in_rows++;
726 				if (Ctrl->Z.active && (n_in_rows < Ctrl->Z.first || n_in_rows > Ctrl->Z.last)) continue;	/* Skip if outside limited record range */
727 				if (!Ctrl->E.active) {
728 					if (Ctrl->T.active[EXCLUDE_DUPLICATES] && row && gmtconvert_is_duplicate_row (S, Ctrl->T.C, Ctrl->T.text, row)) {
729 						n_duplicates++;
730 						continue;	/* Skip duplicate records */
731 					}
732 				}
733 				else if (Ctrl->E.mode < 0) {	/* Only pass first or last or both of them, skipping all others */
734 					if (row > 0 && row < last_row) continue;		/* Always skip the middle of the segment */
735 					if (row == 0 && !(-Ctrl->E.mode & 1)) continue;		/* First record, but we are to skip it */
736 					if (row == last_row && !(-Ctrl->E.mode & 2)) continue;	/* Last record, but we are to skip it */
737 				}
738 				else {	/* Only pass modulo E.mode records (this always includes the first row), if -EM then make sure we also write the last row */
739 					if ((row % Ctrl->E.mode) != 0) {	/* Check if last row and -EM was used */
740 						if (!Ctrl->E.end || row < last_row) continue;
741 					}
742 				}
743 				/* Pull out current virtual row (may consist of a single or many (-A) table rows) */
744 				for (tbl_hor = out_col = tlen = 0; tbl_hor < n_horizontal_tbls; tbl_hor++) {	/* Number of tables to place horizontally */
745 					use_tbl = (Ctrl->A.active) ? tbl_hor : tbl_ver;
746 					Si = D[GMT_IN]->table[use_tbl]->segment[seg];	/* Current input segment in this table */
747 					if (Si->text && Si->text[row]) tlen += strlen (Si->text[row]) + 1;	/* String + separator */
748 					for (col = 0; col < Si->n_columns; col++, out_col++) {	/* Now go across all columns in current table */
749 						val[out_col] = Si->data[col][row];
750 					}
751 				}
752 				for (col = 0; col < n_cols_out; col++) {	/* Now go across the single virtual row */
753 					if (col >= n_cols_in) continue;			/* This column is beyond end of this table */
754 					So->data[col][n_rows] = val[col];
755 				}
756 				if (tlen) {	/* must deal with trailing text(s) */
757 					bool add_separator = false;
758 					if (So->text == NULL) So->text = gmt_M_memory (GMT, NULL, S->n_rows, char *);
759 					So->text[n_rows] = calloc (tlen+1, sizeof(char));	/* Space for trailing \0 */
760 					for (tbl_hor = 0; tbl_hor < n_horizontal_tbls; tbl_hor++) {	/* Number of tables to place horizontally */
761 						use_tbl = (Ctrl->A.active) ? tbl_hor : tbl_ver;
762 						Si = D[GMT_IN]->table[use_tbl]->segment[seg];	/* Current input segment in this table */
763 						if (Si->text && Si->text[row]) {
764 							if (add_separator) strcat (So->text[n_rows], GMT->current.setting.io_col_separator);
765 							strcat (So->text[n_rows], Si->text[row]);
766 							add_separator = true;
767 						}
768 					}
769 				}
770 				n_rows++;
771 			}
772 			SHo->id = out_seg++;
773 			So->n_rows = n_rows;	/* Possibly shorter than originally allocated if -E is used */
774 			D[GMT_OUT]->table[tbl_ver]->n_records += n_rows;
775 			D[GMT_OUT]->n_records = D[GMT_OUT]->table[tbl_ver]->n_records;
776 			if (SHi->ogr) gmt_duplicate_ogr_seg (GMT, So, S);
777 		}
778 		THo->id = tbl_ver;
779 	}
780 	gmt_M_free (GMT, val);
781 
782 	if (Ctrl->I.active) {	/* Must reverse the order of tables, segments and/or records */
783 		uint64_t tbl1, tbl2, row1, row2, seg1, seg2;
784 		void *p = NULL;
785 		if (Ctrl->I.mode & INV_ROWS) {	/* Must actually swap rows */
786 			GMT_Report (API, GMT_MSG_INFORMATION, "Reversing order of records within each segment.\n");
787 			for (tbl = 0; tbl < D[GMT_OUT]->n_tables; tbl++) {	/* Number of output tables */
788 				for (seg = 0; seg < D[GMT_OUT]->table[tbl]->n_segments; seg++) {	/* For each segment in the tables */
789 					for (row1 = 0, row2 = D[GMT_OUT]->table[tbl]->segment[seg]->n_rows - 1; row1 < D[GMT_OUT]->table[tbl]->segment[seg]->n_rows/2; row1++, row2--) {
790 						for (col = 0; col < D[GMT_OUT]->table[tbl]->segment[seg]->n_columns; col++)
791 							gmt_M_double_swap (D[GMT_OUT]->table[tbl]->segment[seg]->data[col][row1], D[GMT_OUT]->table[tbl]->segment[seg]->data[col][row2]);
792 					}
793 				}
794 			}
795 		}
796 		if (Ctrl->I.mode & INV_SEGS) {	/* Must reorder pointers to segments within each table */
797 			GMT_Report (API, GMT_MSG_INFORMATION, "Reversing order of segments within each table.\n");
798 			for (tbl = 0; tbl < D[GMT_OUT]->n_tables; tbl++) {	/* Number of output tables */
799 				for (seg1 = 0, seg2 = D[GMT_OUT]->table[tbl]->n_segments-1; seg1 < D[GMT_OUT]->table[tbl]->n_segments/2; seg1++, seg2--) {	/* For each segment in the table */
800 					p = D[GMT_OUT]->table[tbl]->segment[seg1];
801 					D[GMT_OUT]->table[tbl]->segment[seg1] = D[GMT_OUT]->table[tbl]->segment[seg2];
802 					D[GMT_OUT]->table[tbl]->segment[seg2] = p;
803 				}
804 			}
805 		}
806 		if (Ctrl->I.mode & INV_TBLS) {	/* Must reorder pointers to tables within dataset  */
807 			GMT_Report (API, GMT_MSG_INFORMATION, "Reversing order of tables within the data set.\n");
808 			for (tbl1 = 0, tbl2 = D[GMT_OUT]->n_tables-1; tbl1 < D[GMT_OUT]->n_tables/2; tbl1++, tbl2--) {	/* For each table */
809 				p = D[GMT_OUT]->table[tbl1];
810 				D[GMT_OUT]->table[tbl1] = D[GMT_OUT]->table[tbl2];
811 				D[GMT_OUT]->table[tbl2] = p;
812 			}
813 		}
814 	}
815 
816 	/* Now ready for output */
817 
818 	if (Ctrl->D.active) {	/* Set composite name and io-mode */
819 		gmt_M_str_free (Ctrl->Out.file);
820 		Ctrl->Out.file = strdup (Ctrl->D.name);
821 		DHo->io_mode = Ctrl->D.mode;
822 		if (Ctrl->D.origin) {	/* Update IDs */
823 			for (tbl = 0; tbl < D[GMT_OUT]->n_tables; tbl++) {	/* For each table */
824 				THo = gmt_get_DT_hidden (D[GMT_OUT]->table[tbl]);
825 				THo->id += Ctrl->D.t_orig;
826 				for (seg = 0; seg < D[GMT_OUT]->table[tbl]->n_segments; seg++) {	/* For each segment */
827 					SHo = gmt_get_DS_hidden (D[GMT_OUT]->table[tbl]->segment[seg]);
828 					SHo->id += Ctrl->D.s_orig;
829 				}
830 			}
831 		}
832 	}
833 	else {	/* Just register output to stdout or the given file via ->outfile */
834 		if (GMT->common.a.output)	/* Must notify the machinery of this output type */
835 			DHo->io_mode = GMT_WRITE_OGR;
836 	}
837 
838 	if (Ctrl->T.active[EXCLUDE_HEADERS] && gmt_M_file_is_memory (Ctrl->Out.file) && D[GMT_OUT]->n_segments > 1) {
839 		/* Since no file is written we must physically collate segments into a single segment per table first */
840 		unsigned int flag[3] = {0, 0, GMT_WRITE_SEGMENT};
841 		struct GMT_DATASET *D2 = NULL;
842 		if ((D2 = GMT_Convert_Data (API, D[GMT_OUT], GMT_IS_DATASET, NULL, GMT_IS_DATASET, flag)) == NULL) {
843 			GMT_Report (API, GMT_MSG_ERROR, "Failure while collating each table's segments into a single segment per table.\n");
844 			Return (API->error);
845 		}
846 		if (GMT_Destroy_Data (API, &D[GMT_OUT]) != GMT_NOERROR) {	/* Remove the previously registered output dataset */
847 			Return (API->error);
848 		}
849 		D[GMT_OUT] = D2;	/* Hook up the reformatted dataset */
850 	}
851 	if (Ctrl->N.active) {	/* Sort the output data on selected column before writing */
852 		struct GMT_ORDER *Z = NULL;
853 		uint64_t max_len = 0;
854 		bool do_it = true;
855 		char *way[3] = {"descending", "", "ascending"};
856 		if (Ctrl->N.col >= D[GMT_OUT]->n_columns) {
857 			GMT_Report (API, GMT_MSG_WARNING, "Column selected (%d) as sorting key is outside range of valid columns [0-%d].  No sorting performed\n", (int)Ctrl->N.col, (int)(D[GMT_OUT]->n_columns - 1));
858 			do_it = false;
859 		}
860 		else
861 			GMT_Report (API, GMT_MSG_INFORMATION, "Sort data based on column %d in %s order\n", (int)Ctrl->N.col, way[Ctrl->N.dir+1]);
862 		GMT->current.io.record_type[GMT_OUT] = GMT->current.io.record_type[GMT_IN];
863 		for (tbl = 0; do_it && tbl < D[GMT_OUT]->n_tables; tbl++) {	/* Number of output tables */
864 			for (seg = 0; seg < D[GMT_OUT]->table[tbl]->n_segments; seg++) {	/* For each segment in the tables */
865 				S = D[GMT_OUT]->table[tbl]->segment[seg];	/* Current segment */
866 				if (S->n_rows > max_len) {	/* (Re-)allocate memory for sort order array */
867 					Z = gmt_M_memory (GMT, Z, S->n_rows, struct GMT_ORDER);
868 					max_len =  S->n_rows;
869 				}
870 				for (row = 0; row < S->n_rows; row++) {	/* Load up value/order struct array */
871 					Z[row].value = S->data[Ctrl->N.col][row];
872 					Z[row].order = row;
873 				}
874 				gmt_sort_order (GMT, Z, S->n_rows, Ctrl->N.dir);	/* Sort per segment */
875 				gmt_prep_tmp_arrays (GMT, GMT_OUT, S->n_rows, 1);	/* Init or reallocate tmp vector */
876 				for (col = 0; col < S->n_columns; col++) {
877 					for (row = 0; row < S->n_rows; row++) {	/* Do the shuffle via a temp vector */
878 						GMT->hidden.mem_coord[GMT_X][row] = S->data[col][Z[row].order];
879 						if (S->text) GMT->hidden.mem_txt[row] = S->text[Z[row].order];
880 					}
881 					gmt_M_memcpy (S->data[col], GMT->hidden.mem_coord[GMT_X], S->n_rows, double);
882 					if (S->text) gmt_M_memcpy (S->text, GMT->hidden.mem_txt, S->n_rows, char *);
883 				}
884 			}
885 		}
886 		if (do_it) gmt_M_free (GMT, Z);
887 	}
888 
889 	if (GMT_Write_Data (API, GMT_IS_DATASET, GMT_IS_FILE, D[GMT_IN]->geometry, DHo->io_mode, NULL, Ctrl->Out.file, D[GMT_OUT]) != GMT_NOERROR) {
890 		Return (API->error);
891 	}
892 	if (prevent_seg_headers) GMT->current.io.skip_headers_on_outout = false;	/* Restore to default if it was changed for file output */
893 
894 	GMT_Report (API, GMT_MSG_INFORMATION, "%" PRIu64 " tables %s, %" PRIu64 " records passed (input cols = %d; output cols = %d)\n",
895 		D[GMT_IN]->n_tables, method[Ctrl->A.active], D[GMT_OUT]->n_records, n_cols_in, n_cols_out);
896 	if (Ctrl->Q.active || Ctrl->S.active) GMT_Report (API, GMT_MSG_INFORMATION, "Extracted %" PRIu64 " from a total of %" PRIu64 " segments\n", n_out_seg, D[GMT_OUT]->n_segments);
897 	if (n_duplicates) GMT_Report (API, GMT_MSG_INFORMATION, "Eliminated %" PRIu64 " duplicate records\n", n_duplicates);
898 
899 	Return (GMT_NOERROR);
900 }
901