| /* | 
 |  * psql - the PostgreSQL interactive terminal | 
 |  * | 
 |  * Copyright (c) 2000-2010, PostgreSQL Global Development Group | 
 |  * | 
 |  * src/bin/psql/print.c | 
 |  */ | 
 | #include "postgres_fe.h" | 
 |  | 
 | #include <limits.h> | 
 | #include <math.h> | 
 | #include <signal.h> | 
 | #include <unistd.h> | 
 |  | 
 | #ifndef WIN32 | 
 | #include <sys/ioctl.h>			/* for ioctl() */ | 
 | #endif | 
 |  | 
 | #ifdef HAVE_TERMIOS_H | 
 | #include <termios.h> | 
 | #endif | 
 |  | 
 | #include <locale.h> | 
 |  | 
 | #include "catalog/pg_type.h" | 
 | #include "pqsignal.h" | 
 |  | 
 | #include "common.h" | 
 | #include "mbprint.h" | 
 | #include "print.h" | 
 |  | 
 | /* | 
 |  * We define the cancel_pressed flag in this file, rather than common.c where | 
 |  * it naturally belongs, because this file is also used by non-psql programs | 
 |  * (see the bin/scripts/ directory).  In those programs cancel_pressed will | 
 |  * never become set and will have no effect. | 
 |  * | 
 |  * Note: print.c's general strategy for when to check cancel_pressed is to do | 
 |  * so at completion of each row of output. | 
 |  */ | 
 | volatile bool cancel_pressed = false; | 
 |  | 
 | static char *decimal_point; | 
 | static char *grouping; | 
 | static char *thousands_sep; | 
 |  | 
 | /* Line style control structures */ | 
 | const printTextFormat pg_asciiformat = | 
 | { | 
 | 	"ascii", | 
 | 	{ | 
 | 		{"-", "+", "+", "+"}, | 
 | 		{"-", "+", "+", "+"}, | 
 | 		{"-", "+", "+", "+"}, | 
 | 		{"", "|", "|", "|"} | 
 | 	}, | 
 | 	"|", | 
 | 	"|", | 
 | 	"|", | 
 | 	" ", | 
 | 	"+", | 
 | 	" ", | 
 | 	"+", | 
 | 	".", | 
 | 	".", | 
 | 	true | 
 | }; | 
 |  | 
 | const printTextFormat pg_asciiformat_old = | 
 | { | 
 | 	"old-ascii", | 
 | 	{ | 
 | 		{"-", "+", "+", "+"}, | 
 | 		{"-", "+", "+", "+"}, | 
 | 		{"-", "+", "+", "+"}, | 
 | 		{"", "|", "|", "|"} | 
 | 	}, | 
 | 	":", | 
 | 	";", | 
 | 	" ", | 
 | 	"+", | 
 | 	" ", | 
 | 	" ", | 
 | 	" ", | 
 | 	" ", | 
 | 	" ", | 
 | 	false | 
 | }; | 
 |  | 
 | const printTextFormat pg_utf8format = | 
 | { | 
 | 	"unicode", | 
 | 	{ | 
 | 		/* ─, ┌, ┬, ┐ */ | 
 | 		{"\342\224\200", "\342\224\214", "\342\224\254", "\342\224\220"}, | 
 | 		/* ─, ├, ┼, ┤ */ | 
 | 		{"\342\224\200", "\342\224\234", "\342\224\274", "\342\224\244"}, | 
 | 		/* ─, └, ┴, ┘ */ | 
 | 		{"\342\224\200", "\342\224\224", "\342\224\264", "\342\224\230"}, | 
 | 		/* N/A, │, │, │ */ | 
 | 		{"", "\342\224\202", "\342\224\202", "\342\224\202"} | 
 | 	}, | 
 | 	/* │ */ | 
 | 	"\342\224\202", | 
 | 	/* │ */ | 
 | 	"\342\224\202", | 
 | 	/* │ */ | 
 | 	"\342\224\202", | 
 | 	" ", | 
 | 	/* ↵ */ | 
 | 	"\342\206\265", | 
 | 	" ", | 
 | 	/* ↵ */ | 
 | 	"\342\206\265", | 
 | 	/* … */ | 
 | 	"\342\200\246", | 
 | 	/* … */ | 
 | 	"\342\200\246", | 
 | 	true | 
 | }; | 
 |  | 
 |  | 
 | /* Local functions */ | 
 | static int	strlen_max_width(unsigned char *str, int *target_width, int encoding); | 
 | static void IsPagerNeeded(const printTableContent *cont, const int extra_lines, | 
 | 			  FILE **fout, bool *is_pager); | 
 |  | 
 |  | 
 | static void * | 
 | pg_local_malloc(size_t size) | 
 | { | 
 | 	void	   *tmp; | 
 |  | 
 | 	tmp = malloc(size); | 
 | 	if (!tmp) | 
 | 	{ | 
 | 		fprintf(stderr, _("out of memory\n")); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 | 	return tmp; | 
 | } | 
 |  | 
 | static void * | 
 | pg_local_calloc(int count, size_t size) | 
 | { | 
 | 	void	   *tmp; | 
 |  | 
 | 	tmp = calloc(count, size); | 
 | 	if (!tmp) | 
 | 	{ | 
 | 		fprintf(stderr, _("out of memory\n")); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 | 	return tmp; | 
 | } | 
 |  | 
 | static int | 
 | integer_digits(const char *my_str) | 
 | { | 
 | 	int			frac_len; | 
 |  | 
 | 	if (my_str[0] == '-') | 
 | 		my_str++; | 
 |  | 
 | 	frac_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0; | 
 |  | 
 | 	return strlen(my_str) - frac_len; | 
 | } | 
 |  | 
 | /* Return additional length required for locale-aware numeric output */ | 
 | static int | 
 | additional_numeric_locale_len(const char *my_str) | 
 | { | 
 | 	int			int_len = integer_digits(my_str), | 
 | 				len = 0; | 
 | 	int			groupdigits = atoi(grouping); | 
 |  | 
 | 	if (int_len > 0) | 
 | 		/* Don't count a leading separator */ | 
 | 		len = (int_len / groupdigits - (int_len % groupdigits == 0)) * | 
 | 			strlen(thousands_sep); | 
 |  | 
 | 	if (strchr(my_str, '.') != NULL) | 
 | 		len += strlen(decimal_point) - strlen("."); | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static int | 
 | strlen_with_numeric_locale(const char *my_str) | 
 | { | 
 | 	return strlen(my_str) + additional_numeric_locale_len(my_str); | 
 | } | 
 |  | 
 | /* | 
 |  * Returns the appropriately formatted string in a new allocated block, | 
 |  * caller must free | 
 |  */ | 
 | static char * | 
 | format_numeric_locale(const char *my_str) | 
 | { | 
 | 	int			i, | 
 | 				j, | 
 | 				int_len = integer_digits(my_str), | 
 | 				leading_digits; | 
 | 	int			groupdigits = atoi(grouping); | 
 | 	int			new_str_start = 0; | 
 | 	char	   *new_str = new_str = pg_local_malloc( | 
 | 									 strlen_with_numeric_locale(my_str) + 1); | 
 |  | 
 | 	leading_digits = (int_len % groupdigits != 0) ? | 
 | 		int_len % groupdigits : groupdigits; | 
 |  | 
 | 	if (my_str[0] == '-')		/* skip over sign, affects grouping | 
 | 								 * calculations */ | 
 | 	{ | 
 | 		new_str[0] = my_str[0]; | 
 | 		my_str++; | 
 | 		new_str_start = 1; | 
 | 	} | 
 |  | 
 | 	for (i = 0, j = new_str_start;; i++, j++) | 
 | 	{ | 
 | 		/* Hit decimal point? */ | 
 | 		if (my_str[i] == '.') | 
 | 		{ | 
 | 			strcpy(&new_str[j], decimal_point); | 
 | 			j += strlen(decimal_point); | 
 | 			/* add fractional part */ | 
 | 			strcpy(&new_str[j], &my_str[i] + 1); | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		/* End of string? */ | 
 | 		if (my_str[i] == '\0') | 
 | 		{ | 
 | 			new_str[j] = '\0'; | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		/* Add separator? */ | 
 | 		if (i != 0 && (i - leading_digits) % groupdigits == 0) | 
 | 		{ | 
 | 			strcpy(&new_str[j], thousands_sep); | 
 | 			j += strlen(thousands_sep); | 
 | 		} | 
 |  | 
 | 		new_str[j] = my_str[i]; | 
 | 	} | 
 |  | 
 | 	return new_str; | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  * fputnbytes: print exactly N bytes to a file | 
 |  * | 
 |  * We avoid using %.*s here because it can misbehave if the data | 
 |  * is not valid in what libc thinks is the prevailing encoding. | 
 |  */ | 
 | static void | 
 | fputnbytes(FILE *f, const char *str, size_t n) | 
 | { | 
 | 	while (n-- > 0) | 
 | 		fputc(*str++, f); | 
 | } | 
 |  | 
 |  | 
 | /*************************/ | 
 | /* Unaligned text		 */ | 
 | /*************************/ | 
 |  | 
 |  | 
 | static void | 
 | print_unaligned_text(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	const char *opt_fieldsep = cont->opt->fieldSep; | 
 | 	const char *opt_recordsep = cont->opt->recordSep; | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned int i; | 
 | 	const char *const * ptr; | 
 | 	bool		need_recordsep = false; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (!opt_fieldsep) | 
 | 		opt_fieldsep = ""; | 
 | 	if (!opt_recordsep) | 
 | 		opt_recordsep = ""; | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 			fprintf(fout, "%s%s", cont->title, opt_recordsep); | 
 |  | 
 | 		/* print headers */ | 
 | 		if (!opt_tuples_only) | 
 | 		{ | 
 | 			for (ptr = cont->headers; *ptr; ptr++) | 
 | 			{ | 
 | 				if (ptr != cont->headers) | 
 | 					fputs(opt_fieldsep, fout); | 
 | 				fputs(*ptr, fout); | 
 | 			} | 
 | 			need_recordsep = true; | 
 | 		} | 
 | 	} | 
 | 	else | 
 | 		/* assume continuing printout */ | 
 | 		need_recordsep = true; | 
 |  | 
 | 	/* print cells */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		if (need_recordsep) | 
 | 		{ | 
 | 			fputs(opt_recordsep, fout); | 
 | 			need_recordsep = false; | 
 | 			if (cancel_pressed) | 
 | 				break; | 
 | 		} | 
 | 		fputs(*ptr, fout); | 
 |  | 
 | 		if ((i + 1) % cont->ncolumns) | 
 | 			fputs(opt_fieldsep, fout); | 
 | 		else | 
 | 			need_recordsep = true; | 
 | 	} | 
 |  | 
 | 	/* print footers */ | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 			{ | 
 | 				if (need_recordsep) | 
 | 				{ | 
 | 					fputs(opt_recordsep, fout); | 
 | 					need_recordsep = false; | 
 | 				} | 
 | 				fputs(f->data, fout); | 
 | 				need_recordsep = true; | 
 | 			} | 
 | 		} | 
 | 		/* the last record needs to be concluded with a newline */ | 
 | 		if (need_recordsep) | 
 | 			fputc('\n', fout); | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | print_unaligned_vertical(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	const char *opt_fieldsep = cont->opt->fieldSep; | 
 | 	const char *opt_recordsep = cont->opt->recordSep; | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned int i; | 
 | 	const char *const * ptr; | 
 | 	bool		need_recordsep = false; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (!opt_fieldsep) | 
 | 		opt_fieldsep = ""; | 
 | 	if (!opt_recordsep) | 
 | 		opt_recordsep = ""; | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 		{ | 
 | 			fputs(cont->title, fout); | 
 | 			need_recordsep = true; | 
 | 		} | 
 | 	} | 
 | 	else | 
 | 		/* assume continuing printout */ | 
 | 		need_recordsep = true; | 
 |  | 
 | 	/* print records */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		if (need_recordsep) | 
 | 		{ | 
 | 			/* record separator is 2 occurrences of recordsep in this mode */ | 
 | 			fputs(opt_recordsep, fout); | 
 | 			fputs(opt_recordsep, fout); | 
 | 			need_recordsep = false; | 
 | 			if (cancel_pressed) | 
 | 				break; | 
 | 		} | 
 |  | 
 | 		fputs(cont->headers[i % cont->ncolumns], fout); | 
 | 		fputs(opt_fieldsep, fout); | 
 | 		fputs(*ptr, fout); | 
 |  | 
 | 		if ((i + 1) % cont->ncolumns) | 
 | 			fputs(opt_recordsep, fout); | 
 | 		else | 
 | 			need_recordsep = true; | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		/* print footers */ | 
 | 		if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			fputs(opt_recordsep, fout); | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 			{ | 
 | 				fputs(opt_recordsep, fout); | 
 | 				fputs(f->data, fout); | 
 | 			} | 
 | 		} | 
 |  | 
 | 		fputc('\n', fout); | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | /********************/ | 
 | /* Aligned text		*/ | 
 | /********************/ | 
 |  | 
 |  | 
 | /* draw "line" */ | 
 | static void | 
 | _print_horizontal_line(const unsigned int ncolumns, const unsigned int *widths, | 
 | 					   unsigned short border, printTextRule pos, | 
 | 					   const printTextFormat *format, | 
 | 					   FILE *fout) | 
 | { | 
 | 	const printTextLineFormat *lformat = &format->lrule[pos]; | 
 | 	unsigned int i, | 
 | 				j; | 
 |  | 
 | 	if (border == 1) | 
 | 		fputs(lformat->hrule, fout); | 
 | 	else if (border == 2) | 
 | 		fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule); | 
 |  | 
 | 	for (i = 0; i < ncolumns; i++) | 
 | 	{ | 
 | 		for (j = 0; j < widths[i]; j++) | 
 | 			fputs(lformat->hrule, fout); | 
 |  | 
 | 		if (i < ncolumns - 1) | 
 | 		{ | 
 | 			if (border == 0) | 
 | 				fputc(' ', fout); | 
 | 			else | 
 | 				fprintf(fout, "%s%s%s", lformat->hrule, | 
 | 						lformat->midvrule, lformat->hrule); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (border == 2) | 
 | 		fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule); | 
 | 	else if (border == 1) | 
 | 		fputs(lformat->hrule, fout); | 
 |  | 
 | 	fputc('\n', fout); | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  *	Print pretty boxes around cells. | 
 |  */ | 
 | static void | 
 | print_aligned_text(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	int			encoding = cont->opt->encoding; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	const printTextFormat *format = get_line_style(cont->opt); | 
 | 	const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA]; | 
 |  | 
 | 	unsigned int col_count = 0, | 
 | 				cell_count = 0; | 
 |  | 
 | 	unsigned int i, | 
 | 				j; | 
 |  | 
 | 	unsigned int *width_header, | 
 | 			   *max_width, | 
 | 			   *width_wrap, | 
 | 			   *width_average; | 
 | 	unsigned int *max_nl_lines, /* value split by newlines */ | 
 | 			   *curr_nl_line, | 
 | 			   *max_bytes; | 
 | 	unsigned char **format_buf; | 
 | 	unsigned int width_total; | 
 | 	unsigned int total_header_width; | 
 | 	unsigned int extra_row_output_lines = 0; | 
 | 	unsigned int extra_output_lines = 0; | 
 |  | 
 | 	const char *const * ptr; | 
 |  | 
 | 	struct lineptr **col_lineptrs;		/* pointers to line pointer per column */ | 
 |  | 
 | 	bool	   *header_done;	/* Have all header lines been output? */ | 
 | 	int		   *bytes_output;	/* Bytes output for column value */ | 
 | 	printTextLineWrap *wrap;	/* Wrap status for each column */ | 
 | 	int			output_columns = 0;		/* Width of interactive console */ | 
 | 	bool		is_pager = false; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (opt_border > 2) | 
 | 		opt_border = 2; | 
 |  | 
 | 	if (cont->ncolumns > 0) | 
 | 	{ | 
 | 		col_count = cont->ncolumns; | 
 | 		width_header = pg_local_calloc(col_count, sizeof(*width_header)); | 
 | 		width_average = pg_local_calloc(col_count, sizeof(*width_average)); | 
 | 		max_width = pg_local_calloc(col_count, sizeof(*max_width)); | 
 | 		width_wrap = pg_local_calloc(col_count, sizeof(*width_wrap)); | 
 | 		max_nl_lines = pg_local_calloc(col_count, sizeof(*max_nl_lines)); | 
 | 		curr_nl_line = pg_local_calloc(col_count, sizeof(*curr_nl_line)); | 
 | 		col_lineptrs = pg_local_calloc(col_count, sizeof(*col_lineptrs)); | 
 | 		max_bytes = pg_local_calloc(col_count, sizeof(*max_bytes)); | 
 | 		format_buf = pg_local_calloc(col_count, sizeof(*format_buf)); | 
 | 		header_done = pg_local_calloc(col_count, sizeof(*header_done)); | 
 | 		bytes_output = pg_local_calloc(col_count, sizeof(*bytes_output)); | 
 | 		wrap = pg_local_calloc(col_count, sizeof(*wrap)); | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 		width_header = NULL; | 
 | 		width_average = NULL; | 
 | 		max_width = NULL; | 
 | 		width_wrap = NULL; | 
 | 		max_nl_lines = NULL; | 
 | 		curr_nl_line = NULL; | 
 | 		col_lineptrs = NULL; | 
 | 		max_bytes = NULL; | 
 | 		format_buf = NULL; | 
 | 		header_done = NULL; | 
 | 		bytes_output = NULL; | 
 | 		wrap = NULL; | 
 | 	} | 
 |  | 
 | 	/* scan all column headers, find maximum width and max max_nl_lines */ | 
 | 	for (i = 0; i < col_count; i++) | 
 | 	{ | 
 | 		int			width, | 
 | 					nl_lines, | 
 | 					bytes_required; | 
 |  | 
 | 		pg_wcssize((unsigned char *) cont->headers[i], strlen(cont->headers[i]), | 
 | 				   encoding, &width, &nl_lines, &bytes_required); | 
 | 		if (width > max_width[i]) | 
 | 			max_width[i] = width; | 
 | 		if (nl_lines > max_nl_lines[i]) | 
 | 			max_nl_lines[i] = nl_lines; | 
 | 		if (bytes_required > max_bytes[i]) | 
 | 			max_bytes[i] = bytes_required; | 
 | 		if (nl_lines > extra_row_output_lines) | 
 | 			extra_row_output_lines = nl_lines; | 
 |  | 
 | 		width_header[i] = width; | 
 | 	} | 
 | 	/* Add height of tallest header column */ | 
 | 	extra_output_lines += extra_row_output_lines; | 
 | 	extra_row_output_lines = 0; | 
 |  | 
 | 	/* scan all cells, find maximum width, compute cell_count */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++) | 
 | 	{ | 
 | 		int			width, | 
 | 					nl_lines, | 
 | 					bytes_required; | 
 |  | 
 | 		pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, | 
 | 				   &width, &nl_lines, &bytes_required); | 
 |  | 
 | 		if (width > max_width[i % col_count]) | 
 | 			max_width[i % col_count] = width; | 
 | 		if (nl_lines > max_nl_lines[i % col_count]) | 
 | 			max_nl_lines[i % col_count] = nl_lines; | 
 | 		if (bytes_required > max_bytes[i % col_count]) | 
 | 			max_bytes[i % col_count] = bytes_required; | 
 |  | 
 | 		width_average[i % col_count] += width; | 
 | 	} | 
 |  | 
 | 	/* If we have rows, compute average */ | 
 | 	if (col_count != 0 && cell_count != 0) | 
 | 	{ | 
 | 		int			rows = cell_count / col_count; | 
 |  | 
 | 		for (i = 0; i < col_count; i++) | 
 | 			width_average[i] /= rows; | 
 | 	} | 
 |  | 
 | 	/* adjust the total display width based on border style */ | 
 | 	if (opt_border == 0) | 
 | 		width_total = col_count; | 
 | 	else if (opt_border == 1) | 
 | 		width_total = col_count * 3 - 1; | 
 | 	else | 
 | 		width_total = col_count * 3 + 1; | 
 | 	total_header_width = width_total; | 
 |  | 
 | 	for (i = 0; i < col_count; i++) | 
 | 	{ | 
 | 		width_total += max_width[i]; | 
 | 		total_header_width += width_header[i]; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * At this point: max_width[] contains the max width of each column, | 
 | 	 * max_nl_lines[] contains the max number of lines in each column, | 
 | 	 * max_bytes[] contains the maximum storage space for formatting strings, | 
 | 	 * width_total contains the giant width sum.  Now we allocate some memory | 
 | 	 * for line pointers. | 
 | 	 */ | 
 | 	for (i = 0; i < col_count; i++) | 
 | 	{ | 
 | 		/* Add entry for ptr == NULL array termination */ | 
 | 		col_lineptrs[i] = pg_local_calloc(max_nl_lines[i] + 1, | 
 | 										  sizeof(**col_lineptrs)); | 
 |  | 
 | 		format_buf[i] = pg_local_malloc(max_bytes[i] + 1); | 
 |  | 
 | 		col_lineptrs[i]->ptr = format_buf[i]; | 
 | 	} | 
 |  | 
 | 	/* Default word wrap to the full width, i.e. no word wrap */ | 
 | 	for (i = 0; i < col_count; i++) | 
 | 		width_wrap[i] = max_width[i]; | 
 |  | 
 | 	/* | 
 | 	 * Choose target output width: \pset columns, or $COLUMNS, or ioctl | 
 | 	 */ | 
 | 	if (cont->opt->columns > 0) | 
 | 		output_columns = cont->opt->columns; | 
 | 	else if ((fout == stdout && isatty(fileno(stdout))) || is_pager) | 
 | 	{ | 
 | 		if (cont->opt->env_columns > 0) | 
 | 			output_columns = cont->opt->env_columns; | 
 | #ifdef TIOCGWINSZ | 
 | 		else | 
 | 		{ | 
 | 			struct winsize screen_size; | 
 |  | 
 | 			if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1) | 
 | 				output_columns = screen_size.ws_col; | 
 | 		} | 
 | #endif | 
 | 	} | 
 |  | 
 | 	if (cont->opt->format == PRINT_WRAPPED) | 
 | 	{ | 
 | 		/* | 
 | 		 * Optional optimized word wrap. Shrink columns with a high max/avg | 
 | 		 * ratio.  Slighly bias against wider columns. (Increases chance a | 
 | 		 * narrow column will fit in its cell.)  If available columns is | 
 | 		 * positive...	and greater than the width of the unshrinkable column | 
 | 		 * headers | 
 | 		 */ | 
 | 		if (output_columns > 0 && output_columns >= total_header_width) | 
 | 		{ | 
 | 			/* While there is still excess width... */ | 
 | 			while (width_total > output_columns) | 
 | 			{ | 
 | 				double		max_ratio = 0; | 
 | 				int			worst_col = -1; | 
 |  | 
 | 				/* | 
 | 				 * Find column that has the highest ratio of its maximum width | 
 | 				 * compared to its average width.  This tells us which column | 
 | 				 * will produce the fewest wrapped values if shortened. | 
 | 				 * width_wrap starts as equal to max_width. | 
 | 				 */ | 
 | 				for (i = 0; i < col_count; i++) | 
 | 				{ | 
 | 					if (width_average[i] && width_wrap[i] > width_header[i]) | 
 | 					{ | 
 | 						/* Penalize wide columns by 1% of their width */ | 
 | 						double		ratio; | 
 |  | 
 | 						ratio = (double) width_wrap[i] / width_average[i] + | 
 | 							max_width[i] * 0.01; | 
 | 						if (ratio > max_ratio) | 
 | 						{ | 
 | 							max_ratio = ratio; | 
 | 							worst_col = i; | 
 | 						} | 
 | 					} | 
 | 				} | 
 |  | 
 | 				/* Exit loop if we can't squeeze any more. */ | 
 | 				if (worst_col == -1) | 
 | 					break; | 
 |  | 
 | 				/* Decrease width of target column by one. */ | 
 | 				width_wrap[worst_col]--; | 
 | 				width_total--; | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* If we wrapped beyond the display width, use the pager */ | 
 | 	if (!is_pager && fout == stdout && output_columns > 0 && | 
 | 		(output_columns < total_header_width || output_columns < width_total)) | 
 | 	{ | 
 | 		fout = PageOutput(INT_MAX, cont->opt->pager);	/* force pager */ | 
 | 		is_pager = true; | 
 | 	} | 
 |  | 
 | 	/* Check if newlines or our wrapping now need the pager */ | 
 | 	if (!is_pager) | 
 | 	{ | 
 | 		/* scan all cells, find maximum width, compute cell_count */ | 
 | 		for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++) | 
 | 		{ | 
 | 			int			width, | 
 | 						nl_lines, | 
 | 						bytes_required; | 
 |  | 
 | 			pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, | 
 | 					   &width, &nl_lines, &bytes_required); | 
 |  | 
 | 			/* | 
 | 			 * A row can have both wrapping and newlines that cause it to | 
 | 			 * display across multiple lines.  We check for both cases below. | 
 | 			 */ | 
 | 			if (width > 0 && width_wrap[i]) | 
 | 			{ | 
 | 				unsigned int extra_lines; | 
 |  | 
 | 				extra_lines = (width - 1) / width_wrap[i] + nl_lines; | 
 | 				if (extra_lines > extra_row_output_lines) | 
 | 					extra_row_output_lines = extra_lines; | 
 | 			} | 
 |  | 
 | 			/* i is the current column number: increment with wrap */ | 
 | 			if (++i >= col_count) | 
 | 			{ | 
 | 				i = 0; | 
 | 				/* At last column of each row, add tallest column height */ | 
 | 				extra_output_lines += extra_row_output_lines; | 
 | 				extra_row_output_lines = 0; | 
 | 			} | 
 | 		} | 
 | 		IsPagerNeeded(cont, extra_output_lines, &fout, &is_pager); | 
 | 	} | 
 |  | 
 | 	/* time to output */ | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		/* print title */ | 
 | 		if (cont->title && !opt_tuples_only) | 
 | 		{ | 
 | 			int			width, | 
 | 						height; | 
 |  | 
 | 			pg_wcssize((unsigned char *) cont->title, strlen(cont->title), | 
 | 					   encoding, &width, &height, NULL); | 
 | 			if (width >= width_total) | 
 | 				/* Aligned */ | 
 | 				fprintf(fout, "%s\n", cont->title); | 
 | 			else | 
 | 				/* Centered */ | 
 | 				fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "", | 
 | 						cont->title); | 
 | 		} | 
 |  | 
 | 		/* print headers */ | 
 | 		if (!opt_tuples_only) | 
 | 		{ | 
 | 			int			more_col_wrapping; | 
 | 			int			curr_nl_line; | 
 |  | 
 | 			if (opt_border == 2) | 
 | 				_print_horizontal_line(col_count, width_wrap, opt_border, | 
 | 									   PRINT_RULE_TOP, format, fout); | 
 |  | 
 | 			for (i = 0; i < col_count; i++) | 
 | 				pg_wcsformat((unsigned char *) cont->headers[i], | 
 | 							 strlen(cont->headers[i]), encoding, | 
 | 							 col_lineptrs[i], max_nl_lines[i]); | 
 |  | 
 | 			more_col_wrapping = col_count; | 
 | 			curr_nl_line = 0; | 
 | 			memset(header_done, false, col_count * sizeof(bool)); | 
 | 			while (more_col_wrapping) | 
 | 			{ | 
 | 				if (opt_border == 2) | 
 | 					fputs(dformat->leftvrule, fout); | 
 |  | 
 | 				for (i = 0; i < cont->ncolumns; i++) | 
 | 				{ | 
 | 					struct lineptr *this_line = col_lineptrs[i] + curr_nl_line; | 
 | 					unsigned int nbspace; | 
 |  | 
 | 					if (opt_border != 0 || | 
 | 						(format->wrap_right_border == false && i > 0)) | 
 | 						fputs(curr_nl_line ? format->header_nl_left : " ", | 
 | 							  fout); | 
 |  | 
 | 					if (!header_done[i]) | 
 | 					{ | 
 | 						nbspace = width_wrap[i] - this_line->width; | 
 |  | 
 | 						/* centered */ | 
 | 						fprintf(fout, "%-*s%s%-*s", | 
 | 								nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, ""); | 
 |  | 
 | 						if (!(this_line + 1)->ptr) | 
 | 						{ | 
 | 							more_col_wrapping--; | 
 | 							header_done[i] = 1; | 
 | 						} | 
 | 					} | 
 | 					else | 
 | 						fprintf(fout, "%*s", width_wrap[i], ""); | 
 |  | 
 | 					if (opt_border != 0 || format->wrap_right_border == true) | 
 | 						fputs(!header_done[i] ? format->header_nl_right : " ", | 
 | 							  fout); | 
 |  | 
 | 					if (opt_border != 0 && i < col_count - 1) | 
 | 						fputs(dformat->midvrule, fout); | 
 | 				} | 
 | 				curr_nl_line++; | 
 |  | 
 | 				if (opt_border == 2) | 
 | 					fputs(dformat->rightvrule, fout); | 
 | 				fputc('\n', fout); | 
 | 			} | 
 |  | 
 | 			_print_horizontal_line(col_count, width_wrap, opt_border, | 
 | 								   PRINT_RULE_MIDDLE, format, fout); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* print cells, one loop per row */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i += col_count, ptr += col_count) | 
 | 	{ | 
 | 		bool		more_lines; | 
 |  | 
 | 		if (cancel_pressed) | 
 | 			break; | 
 |  | 
 | 		/* | 
 | 		 * Format each cell. | 
 | 		 */ | 
 | 		for (j = 0; j < col_count; j++) | 
 | 		{ | 
 | 			pg_wcsformat((unsigned char *) ptr[j], strlen(ptr[j]), encoding, | 
 | 						 col_lineptrs[j], max_nl_lines[j]); | 
 | 			curr_nl_line[j] = 0; | 
 | 		} | 
 |  | 
 | 		memset(bytes_output, 0, col_count * sizeof(int)); | 
 |  | 
 | 		/* | 
 | 		 * Each time through this loop, one display line is output. It can | 
 | 		 * either be a full value or a partial value if embedded newlines | 
 | 		 * exist or if 'format=wrapping' mode is enabled. | 
 | 		 */ | 
 | 		do | 
 | 		{ | 
 | 			more_lines = false; | 
 |  | 
 | 			/* left border */ | 
 | 			if (opt_border == 2) | 
 | 				fputs(dformat->leftvrule, fout); | 
 |  | 
 | 			/* for each column */ | 
 | 			for (j = 0; j < col_count; j++) | 
 | 			{ | 
 | 				/* We have a valid array element, so index it */ | 
 | 				struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]]; | 
 | 				int			bytes_to_output; | 
 | 				int			chars_to_output = width_wrap[j]; | 
 | 				bool		finalspaces = (opt_border == 2 || j < col_count - 1); | 
 |  | 
 | 				/* Print left-hand wrap or newline mark */ | 
 | 				if (opt_border != 0) | 
 | 				{ | 
 | 					if (wrap[j] == PRINT_LINE_WRAP_WRAP) | 
 | 						fputs(format->wrap_left, fout); | 
 | 					else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE) | 
 | 						fputs(format->nl_left, fout); | 
 | 					else | 
 | 						fputc(' ', fout); | 
 | 				} | 
 |  | 
 | 				if (!this_line->ptr) | 
 | 				{ | 
 | 					/* Past newline lines so just pad for other columns */ | 
 | 					if (finalspaces) | 
 | 						fprintf(fout, "%*s", chars_to_output, ""); | 
 | 				} | 
 | 				else | 
 | 				{ | 
 | 					/* Get strlen() of the characters up to width_wrap */ | 
 | 					bytes_to_output = | 
 | 						strlen_max_width(this_line->ptr + bytes_output[j], | 
 | 										 &chars_to_output, encoding); | 
 |  | 
 | 					/* | 
 | 					 * If we exceeded width_wrap, it means the display width | 
 | 					 * of a single character was wider than our target width. | 
 | 					 * In that case, we have to pretend we are only printing | 
 | 					 * the target display width and make the best of it. | 
 | 					 */ | 
 | 					if (chars_to_output > width_wrap[j]) | 
 | 						chars_to_output = width_wrap[j]; | 
 |  | 
 | 					if (cont->aligns[j] == 'r') /* Right aligned cell */ | 
 | 					{ | 
 | 						/* spaces first */ | 
 | 						fprintf(fout, "%*s", width_wrap[j] - chars_to_output, ""); | 
 | 						fputnbytes(fout, | 
 | 								 (char *) (this_line->ptr + bytes_output[j]), | 
 | 								   bytes_to_output); | 
 | 					} | 
 | 					else	/* Left aligned cell */ | 
 | 					{ | 
 | 						/* spaces second */ | 
 | 						fputnbytes(fout, | 
 | 								 (char *) (this_line->ptr + bytes_output[j]), | 
 | 								   bytes_to_output); | 
 | 					} | 
 |  | 
 | 					bytes_output[j] += bytes_to_output; | 
 |  | 
 | 					/* Do we have more text to wrap? */ | 
 | 					if (*(this_line->ptr + bytes_output[j]) != '\0') | 
 | 						more_lines = true; | 
 | 					else | 
 | 					{ | 
 | 						/* Advance to next newline line */ | 
 | 						curr_nl_line[j]++; | 
 | 						if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL) | 
 | 							more_lines = true; | 
 | 						bytes_output[j] = 0; | 
 | 					} | 
 | 				} | 
 |  | 
 | 				/* Determine next line's wrap status for this column */ | 
 | 				wrap[j] = PRINT_LINE_WRAP_NONE; | 
 | 				if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL) | 
 | 				{ | 
 | 					if (bytes_output[j] != 0) | 
 | 						wrap[j] = PRINT_LINE_WRAP_WRAP; | 
 | 					else if (curr_nl_line[j] != 0) | 
 | 						wrap[j] = PRINT_LINE_WRAP_NEWLINE; | 
 | 				} | 
 |  | 
 | 				/* | 
 | 				 * If left-aligned, pad out remaining space if needed (not | 
 | 				 * last column, and/or wrap marks required). | 
 | 				 */ | 
 | 				if (cont->aligns[j] != 'r')		/* Left aligned cell */ | 
 | 				{ | 
 | 					if (finalspaces || | 
 | 						wrap[j] == PRINT_LINE_WRAP_WRAP || | 
 | 						wrap[j] == PRINT_LINE_WRAP_NEWLINE) | 
 | 						fprintf(fout, "%*s", | 
 | 								width_wrap[j] - chars_to_output, ""); | 
 | 				} | 
 |  | 
 | 				/* Print right-hand wrap or newline mark */ | 
 | 				if (wrap[j] == PRINT_LINE_WRAP_WRAP) | 
 | 					fputs(format->wrap_right, fout); | 
 | 				else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE) | 
 | 					fputs(format->nl_right, fout); | 
 | 				else if (opt_border == 2 || j < col_count - 1) | 
 | 					fputc(' ', fout); | 
 |  | 
 | 				/* Print column divider, if not the last column */ | 
 | 				if (opt_border != 0 && j < col_count - 1) | 
 | 				{ | 
 | 					if (wrap[j + 1] == PRINT_LINE_WRAP_WRAP) | 
 | 						fputs(format->midvrule_wrap, fout); | 
 | 					else if (wrap[j + 1] == PRINT_LINE_WRAP_NEWLINE) | 
 | 						fputs(format->midvrule_nl, fout); | 
 | 					else if (col_lineptrs[j + 1][curr_nl_line[j + 1]].ptr == NULL) | 
 | 						fputs(format->midvrule_blank, fout); | 
 | 					else | 
 | 						fputs(dformat->midvrule, fout); | 
 | 				} | 
 | 			} | 
 |  | 
 | 			/* end-of-row border */ | 
 | 			if (opt_border == 2) | 
 | 				fputs(dformat->rightvrule, fout); | 
 | 			fputc('\n', fout); | 
 |  | 
 | 		} while (more_lines); | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		if (opt_border == 2 && !cancel_pressed) | 
 | 			_print_horizontal_line(col_count, width_wrap, opt_border, | 
 | 								   PRINT_RULE_BOTTOM, format, fout); | 
 |  | 
 | 		/* print footers */ | 
 | 		if (cont->footers && !opt_tuples_only && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 				fprintf(fout, "%s\n", f->data); | 
 | 		} | 
 |  | 
 | 		fputc('\n', fout); | 
 | 	} | 
 |  | 
 | 	/* clean up */ | 
 | 	for (i = 0; i < col_count; i++) | 
 | 	{ | 
 | 		free(col_lineptrs[i]); | 
 | 		free(format_buf[i]); | 
 | 	} | 
 | 	free(width_header); | 
 | 	free(width_average); | 
 | 	free(max_width); | 
 | 	free(width_wrap); | 
 | 	free(max_nl_lines); | 
 | 	free(curr_nl_line); | 
 | 	free(col_lineptrs); | 
 | 	free(max_bytes); | 
 | 	free(format_buf); | 
 | 	free(header_done); | 
 | 	free(bytes_output); | 
 | 	free(wrap); | 
 |  | 
 | 	if (is_pager) | 
 | 		ClosePager(fout); | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | print_aligned_vertical_line(const printTableContent *cont, | 
 | 							unsigned long record, | 
 | 							unsigned int hwidth, | 
 | 							unsigned int dwidth, | 
 | 							printTextRule pos, | 
 | 							FILE *fout) | 
 | { | 
 | 	const printTextFormat *format = get_line_style(cont->opt); | 
 | 	const printTextLineFormat *lformat = &format->lrule[pos]; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	unsigned int i; | 
 | 	int			reclen = 0; | 
 |  | 
 | 	if (opt_border == 2) | 
 | 		fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule); | 
 | 	else if (opt_border == 1) | 
 | 		fputs(lformat->hrule, fout); | 
 |  | 
 | 	if (record) | 
 | 	{ | 
 | 		if (opt_border == 0) | 
 | 			reclen = fprintf(fout, "* Record %lu", record); | 
 | 		else | 
 | 			reclen = fprintf(fout, "[ RECORD %lu ]", record); | 
 | 	} | 
 | 	if (opt_border != 2) | 
 | 		reclen++; | 
 | 	if (reclen < 0) | 
 | 		reclen = 0; | 
 | 	for (i = reclen; i < hwidth; i++) | 
 | 		fputs(opt_border > 0 ? lformat->hrule : " ", fout); | 
 | 	reclen -= hwidth; | 
 |  | 
 | 	if (opt_border > 0) | 
 | 	{ | 
 | 		if (reclen-- <= 0) | 
 | 			fputs(lformat->hrule, fout); | 
 | 		if (reclen-- <= 0) | 
 | 			fputs(lformat->midvrule, fout); | 
 | 		if (reclen-- <= 0) | 
 | 			fputs(lformat->hrule, fout); | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 		if (reclen-- <= 0) | 
 | 			fputc(' ', fout); | 
 | 	} | 
 | 	if (reclen < 0) | 
 | 		reclen = 0; | 
 | 	for (i = reclen; i < dwidth; i++) | 
 | 		fputs(opt_border > 0 ? lformat->hrule : " ", fout); | 
 | 	if (opt_border == 2) | 
 | 		fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule); | 
 | 	fputc('\n', fout); | 
 | } | 
 |  | 
 | static void | 
 | print_aligned_vertical(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	const printTextFormat *format = get_line_style(cont->opt); | 
 | 	const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA]; | 
 | 	int			encoding = cont->opt->encoding; | 
 | 	unsigned long record = cont->opt->prior_records + 1; | 
 | 	const char *const * ptr; | 
 | 	unsigned int i, | 
 | 				hwidth = 0, | 
 | 				dwidth = 0, | 
 | 				hheight = 1, | 
 | 				dheight = 1, | 
 | 				hformatsize = 0, | 
 | 				dformatsize = 0; | 
 | 	struct lineptr *hlineptr, | 
 | 			   *dlineptr; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (opt_border > 2) | 
 | 		opt_border = 2; | 
 |  | 
 | 	if (cont->cells[0] == NULL && cont->opt->start_table && | 
 | 		cont->opt->stop_table) | 
 | 	{ | 
 | 		fprintf(fout, _("(No rows)\n")); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* Find the maximum dimensions for the headers */ | 
 | 	for (i = 0; i < cont->ncolumns; i++) | 
 | 	{ | 
 | 		int			width, | 
 | 					height, | 
 | 					fs; | 
 |  | 
 | 		pg_wcssize((unsigned char *) cont->headers[i], strlen(cont->headers[i]), | 
 | 				   encoding, &width, &height, &fs); | 
 | 		if (width > hwidth) | 
 | 			hwidth = width; | 
 | 		if (height > hheight) | 
 | 			hheight = height; | 
 | 		if (fs > hformatsize) | 
 | 			hformatsize = fs; | 
 | 	} | 
 |  | 
 | 	/* find longest data cell */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; ptr++, i++) | 
 | 	{ | 
 | 		int			width, | 
 | 					height, | 
 | 					fs; | 
 |  | 
 | 		pg_wcssize((unsigned char *) *ptr, strlen(*ptr), encoding, | 
 | 				   &width, &height, &fs); | 
 | 		if (width > dwidth) | 
 | 			dwidth = width; | 
 | 		if (height > dheight) | 
 | 			dheight = height; | 
 | 		if (fs > dformatsize) | 
 | 			dformatsize = fs; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * We now have all the information we need to setup the formatting | 
 | 	 * structures | 
 | 	 */ | 
 | 	dlineptr = pg_local_malloc(sizeof(*dlineptr) * (1+dheight)); | 
 | 	hlineptr = pg_local_malloc(sizeof(*hlineptr) * (1+hheight)); | 
 |  | 
 | 	dlineptr->ptr = pg_local_malloc(dformatsize); | 
 | 	hlineptr->ptr = pg_local_malloc(hformatsize); | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 			fprintf(fout, "%s\n", cont->title); | 
 | 	} | 
 |  | 
 | 	/* print records */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		printTextRule pos; | 
 | 		int			line_count, | 
 | 					dcomplete, | 
 | 					hcomplete; | 
 |  | 
 | 		if (cancel_pressed) | 
 | 			break; | 
 |  | 
 | 		if (i == 0) | 
 | 			pos = PRINT_RULE_TOP; | 
 | 		else if (!(*(ptr + 1))) | 
 | 			pos = PRINT_RULE_BOTTOM; | 
 | 		else | 
 | 			pos = PRINT_RULE_MIDDLE; | 
 |  | 
 | 		if (i % cont->ncolumns == 0) | 
 | 		{ | 
 | 			if (!opt_tuples_only) | 
 | 				print_aligned_vertical_line(cont, record++, hwidth, dwidth, | 
 | 											pos, fout); | 
 | 			else if (i != 0 || !cont->opt->start_table || opt_border == 2) | 
 | 				print_aligned_vertical_line(cont, 0, hwidth, dwidth, | 
 | 											pos, fout); | 
 | 		} | 
 |  | 
 | 		/* Format the header */ | 
 | 		pg_wcsformat((unsigned char *) cont->headers[i % cont->ncolumns], | 
 | 					 strlen(cont->headers[i % cont->ncolumns]), | 
 | 					 encoding, hlineptr, hheight); | 
 | 		/* Format the data */ | 
 | 		pg_wcsformat((unsigned char *) *ptr, strlen(*ptr), encoding, | 
 | 					 dlineptr, dheight); | 
 |  | 
 | 		line_count = 0; | 
 | 		dcomplete = hcomplete = 0; | 
 | 		while (!dcomplete || !hcomplete) | 
 | 		{ | 
 | 			if (opt_border == 2) | 
 | 				fprintf(fout, "%s ", dformat->leftvrule); | 
 | 			if (!hcomplete) | 
 | 			{ | 
 | 				fprintf(fout, "%-s%*s", hlineptr[line_count].ptr, | 
 | 						hwidth - hlineptr[line_count].width, ""); | 
 |  | 
 | 				if (!hlineptr[line_count + 1].ptr) | 
 | 					hcomplete = 1; | 
 | 			} | 
 | 			else | 
 | 				fprintf(fout, "%*s", hwidth, ""); | 
 |  | 
 | 			if (opt_border > 0) | 
 | 				fprintf(fout, " %s ", dformat->midvrule); | 
 | 			else | 
 | 				fputc(' ', fout); | 
 |  | 
 | 			if (!dcomplete) | 
 | 			{ | 
 | 				if (opt_border < 2) | 
 | 					fprintf(fout, "%s\n", dlineptr[line_count].ptr); | 
 | 				else | 
 | 					fprintf(fout, "%-s%*s %s\n", dlineptr[line_count].ptr, | 
 | 							dwidth - dlineptr[line_count].width, "", | 
 | 							dformat->rightvrule); | 
 |  | 
 | 				if (!dlineptr[line_count + 1].ptr) | 
 | 					dcomplete = 1; | 
 | 			} | 
 | 			else | 
 | 			{ | 
 | 				if (opt_border < 2) | 
 | 					fputc('\n', fout); | 
 | 				else | 
 | 					fprintf(fout, "%*s %s\n", dwidth, "", dformat->rightvrule); | 
 | 			} | 
 | 			line_count++; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		if (opt_border == 2 && !cancel_pressed) | 
 | 			print_aligned_vertical_line(cont, 0, hwidth, dwidth, | 
 | 										PRINT_RULE_BOTTOM, fout); | 
 |  | 
 | 		/* print footers */ | 
 | 		if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			if (opt_border < 2) | 
 | 				fputc('\n', fout); | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 				fprintf(fout, "%s\n", f->data); | 
 | 		} | 
 |  | 
 | 		fputc('\n', fout); | 
 | 	} | 
 |  | 
 | 	free(hlineptr->ptr); | 
 | 	free(dlineptr->ptr); | 
 | 	free(hlineptr); | 
 | 	free(dlineptr); | 
 | } | 
 |  | 
 |  | 
 | /**********************/ | 
 | /* HTML printing ******/ | 
 | /**********************/ | 
 |  | 
 |  | 
 | void | 
 | html_escaped_print(const char *in, FILE *fout) | 
 | { | 
 | 	const char *p; | 
 | 	bool		leading_space = true; | 
 |  | 
 | 	for (p = in; *p; p++) | 
 | 	{ | 
 | 		switch (*p) | 
 | 		{ | 
 | 			case '&': | 
 | 				fputs("&", fout); | 
 | 				break; | 
 | 			case '<': | 
 | 				fputs("<", fout); | 
 | 				break; | 
 | 			case '>': | 
 | 				fputs(">", fout); | 
 | 				break; | 
 | 			case '\n': | 
 | 				fputs("<br />\n", fout); | 
 | 				break; | 
 | 			case '"': | 
 | 				fputs(""", fout); | 
 | 				break; | 
 | 			case ' ': | 
 | 				/* protect leading space, for EXPLAIN output */ | 
 | 				if (leading_space) | 
 | 					fputs(" ", fout); | 
 | 				else | 
 | 					fputs(" ", fout); | 
 | 				break; | 
 | 			default: | 
 | 				fputc(*p, fout); | 
 | 		} | 
 | 		if (*p != ' ') | 
 | 			leading_space = false; | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | print_html_text(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	const char *opt_table_attr = cont->opt->tableAttr; | 
 | 	unsigned int i; | 
 | 	const char *const * ptr; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		fprintf(fout, "<table border=\"%d\"", opt_border); | 
 | 		if (opt_table_attr) | 
 | 			fprintf(fout, " %s", opt_table_attr); | 
 | 		fputs(">\n", fout); | 
 |  | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 		{ | 
 | 			fputs("  <caption>", fout); | 
 | 			html_escaped_print(cont->title, fout); | 
 | 			fputs("</caption>\n", fout); | 
 | 		} | 
 |  | 
 | 		/* print headers */ | 
 | 		if (!opt_tuples_only) | 
 | 		{ | 
 | 			fputs("  <tr>\n", fout); | 
 | 			for (ptr = cont->headers; *ptr; ptr++) | 
 | 			{ | 
 | 				fputs("    <th align=\"center\">", fout); | 
 | 				html_escaped_print(*ptr, fout); | 
 | 				fputs("</th>\n", fout); | 
 | 			} | 
 | 			fputs("  </tr>\n", fout); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* print cells */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		if (i % cont->ncolumns == 0) | 
 | 		{ | 
 | 			if (cancel_pressed) | 
 | 				break; | 
 | 			fputs("  <tr valign=\"top\">\n", fout); | 
 | 		} | 
 |  | 
 | 		fprintf(fout, "    <td align=\"%s\">", cont->aligns[(i) % cont->ncolumns] == 'r' ? "right" : "left"); | 
 | 		/* is string only whitespace? */ | 
 | 		if ((*ptr)[strspn(*ptr, " \t")] == '\0') | 
 | 			fputs("  ", fout); | 
 | 		else | 
 | 			html_escaped_print(*ptr, fout); | 
 |  | 
 | 		fputs("</td>\n", fout); | 
 |  | 
 | 		if ((i + 1) % cont->ncolumns == 0) | 
 | 			fputs("  </tr>\n", fout); | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		fputs("</table>\n", fout); | 
 |  | 
 | 		/* print footers */ | 
 | 		if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			fputs("<p>", fout); | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 			{ | 
 | 				html_escaped_print(f->data, fout); | 
 | 				fputs("<br />\n", fout); | 
 | 			} | 
 | 			fputs("</p>", fout); | 
 | 		} | 
 |  | 
 | 		fputc('\n', fout); | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | print_html_vertical(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	const char *opt_table_attr = cont->opt->tableAttr; | 
 | 	unsigned long record = cont->opt->prior_records + 1; | 
 | 	unsigned int i; | 
 | 	const char *const * ptr; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		fprintf(fout, "<table border=\"%d\"", opt_border); | 
 | 		if (opt_table_attr) | 
 | 			fprintf(fout, " %s", opt_table_attr); | 
 | 		fputs(">\n", fout); | 
 |  | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 		{ | 
 | 			fputs("  <caption>", fout); | 
 | 			html_escaped_print(cont->title, fout); | 
 | 			fputs("</caption>\n", fout); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* print records */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		if (i % cont->ncolumns == 0) | 
 | 		{ | 
 | 			if (cancel_pressed) | 
 | 				break; | 
 | 			if (!opt_tuples_only) | 
 | 				fprintf(fout, | 
 | 						"\n  <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n", | 
 | 						record++); | 
 | 			else | 
 | 				fputs("\n  <tr><td colspan=\"2\"> </td></tr>\n", fout); | 
 | 		} | 
 | 		fputs("  <tr valign=\"top\">\n" | 
 | 			  "    <th>", fout); | 
 | 		html_escaped_print(cont->headers[i % cont->ncolumns], fout); | 
 | 		fputs("</th>\n", fout); | 
 |  | 
 | 		fprintf(fout, "    <td align=\"%s\">", cont->aligns[i % cont->ncolumns] == 'r' ? "right" : "left"); | 
 | 		/* is string only whitespace? */ | 
 | 		if ((*ptr)[strspn(*ptr, " \t")] == '\0') | 
 | 			fputs("  ", fout); | 
 | 		else | 
 | 			html_escaped_print(*ptr, fout); | 
 |  | 
 | 		fputs("</td>\n  </tr>\n", fout); | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		fputs("</table>\n", fout); | 
 |  | 
 | 		/* print footers */ | 
 | 		if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			fputs("<p>", fout); | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 			{ | 
 | 				html_escaped_print(f->data, fout); | 
 | 				fputs("<br />\n", fout); | 
 | 			} | 
 | 			fputs("</p>", fout); | 
 | 		} | 
 |  | 
 | 		fputc('\n', fout); | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | /*************************/ | 
 | /* LaTeX				 */ | 
 | /*************************/ | 
 |  | 
 |  | 
 | static void | 
 | latex_escaped_print(const char *in, FILE *fout) | 
 | { | 
 | 	const char *p; | 
 |  | 
 | 	for (p = in; *p; p++) | 
 | 		switch (*p) | 
 | 		{ | 
 | 			case '&': | 
 | 				fputs("\\&", fout); | 
 | 				break; | 
 | 			case '%': | 
 | 				fputs("\\%", fout); | 
 | 				break; | 
 | 			case '$': | 
 | 				fputs("\\$", fout); | 
 | 				break; | 
 | 			case '_': | 
 | 				fputs("\\_", fout); | 
 | 				break; | 
 | 			case '{': | 
 | 				fputs("\\{", fout); | 
 | 				break; | 
 | 			case '}': | 
 | 				fputs("\\}", fout); | 
 | 				break; | 
 | 			case '\\': | 
 | 				fputs("\\backslash", fout); | 
 | 				break; | 
 | 			case '\n': | 
 | 				fputs("\\\\", fout); | 
 | 				break; | 
 | 			default: | 
 | 				fputc(*p, fout); | 
 | 		} | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | print_latex_text(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	unsigned int i; | 
 | 	const char *const * ptr; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (opt_border > 2) | 
 | 		opt_border = 2; | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 		{ | 
 | 			fputs("\\begin{center}\n", fout); | 
 | 			latex_escaped_print(cont->title, fout); | 
 | 			fputs("\n\\end{center}\n\n", fout); | 
 | 		} | 
 |  | 
 | 		/* begin environment and set alignments and borders */ | 
 | 		fputs("\\begin{tabular}{", fout); | 
 |  | 
 | 		if (opt_border == 2) | 
 | 			fputs("| ", fout); | 
 | 		for (i = 0; i < cont->ncolumns; i++) | 
 | 		{ | 
 | 			fputc(*(cont->aligns + i), fout); | 
 | 			if (opt_border != 0 && i < cont->ncolumns - 1) | 
 | 				fputs(" | ", fout); | 
 | 		} | 
 | 		if (opt_border == 2) | 
 | 			fputs(" |", fout); | 
 |  | 
 | 		fputs("}\n", fout); | 
 |  | 
 | 		if (!opt_tuples_only && opt_border == 2) | 
 | 			fputs("\\hline\n", fout); | 
 |  | 
 | 		/* print headers */ | 
 | 		if (!opt_tuples_only) | 
 | 		{ | 
 | 			for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++) | 
 | 			{ | 
 | 				if (i != 0) | 
 | 					fputs(" & ", fout); | 
 | 				fputs("\\textit{", fout); | 
 | 				latex_escaped_print(*ptr, fout); | 
 | 				fputc('}', fout); | 
 | 			} | 
 | 			fputs(" \\\\\n", fout); | 
 | 			fputs("\\hline\n", fout); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* print cells */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		latex_escaped_print(*ptr, fout); | 
 |  | 
 | 		if ((i + 1) % cont->ncolumns == 0) | 
 | 		{ | 
 | 			fputs(" \\\\\n", fout); | 
 | 			if (cancel_pressed) | 
 | 				break; | 
 | 		} | 
 | 		else | 
 | 			fputs(" & ", fout); | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		if (opt_border == 2) | 
 | 			fputs("\\hline\n", fout); | 
 |  | 
 | 		fputs("\\end{tabular}\n\n\\noindent ", fout); | 
 |  | 
 | 		/* print footers */ | 
 | 		if (cont->footers && !opt_tuples_only && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 			{ | 
 | 				latex_escaped_print(f->data, fout); | 
 | 				fputs(" \\\\\n", fout); | 
 | 			} | 
 | 		} | 
 |  | 
 | 		fputc('\n', fout); | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | print_latex_vertical(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	unsigned long record = cont->opt->prior_records + 1; | 
 | 	unsigned int i; | 
 | 	const char *const * ptr; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (opt_border > 2) | 
 | 		opt_border = 2; | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 		{ | 
 | 			fputs("\\begin{center}\n", fout); | 
 | 			latex_escaped_print(cont->title, fout); | 
 | 			fputs("\n\\end{center}\n\n", fout); | 
 | 		} | 
 |  | 
 | 		/* begin environment and set alignments and borders */ | 
 | 		fputs("\\begin{tabular}{", fout); | 
 | 		if (opt_border == 0) | 
 | 			fputs("cl", fout); | 
 | 		else if (opt_border == 1) | 
 | 			fputs("c|l", fout); | 
 | 		else if (opt_border == 2) | 
 | 			fputs("|c|l|", fout); | 
 | 		fputs("}\n", fout); | 
 | 	} | 
 |  | 
 | 	/* print records */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		/* new record */ | 
 | 		if (i % cont->ncolumns == 0) | 
 | 		{ | 
 | 			if (cancel_pressed) | 
 | 				break; | 
 | 			if (!opt_tuples_only) | 
 | 			{ | 
 | 				if (opt_border == 2) | 
 | 				{ | 
 | 					fputs("\\hline\n", fout); | 
 | 					fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++); | 
 | 				} | 
 | 				else | 
 | 					fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %lu}} \\\\\n", record++); | 
 | 			} | 
 | 			if (opt_border >= 1) | 
 | 				fputs("\\hline\n", fout); | 
 | 		} | 
 |  | 
 | 		latex_escaped_print(cont->headers[i % cont->ncolumns], fout); | 
 | 		fputs(" & ", fout); | 
 | 		latex_escaped_print(*ptr, fout); | 
 | 		fputs(" \\\\\n", fout); | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		if (opt_border == 2) | 
 | 			fputs("\\hline\n", fout); | 
 |  | 
 | 		fputs("\\end{tabular}\n\n\\noindent ", fout); | 
 |  | 
 | 		/* print footers */ | 
 | 		if (cont->footers && !opt_tuples_only && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 			{ | 
 | 				latex_escaped_print(f->data, fout); | 
 | 				fputs(" \\\\\n", fout); | 
 | 			} | 
 | 		} | 
 |  | 
 | 		fputc('\n', fout); | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | /*************************/ | 
 | /* Troff -ms		 */ | 
 | /*************************/ | 
 |  | 
 |  | 
 | static void | 
 | troff_ms_escaped_print(const char *in, FILE *fout) | 
 | { | 
 | 	const char *p; | 
 |  | 
 | 	for (p = in; *p; p++) | 
 | 		switch (*p) | 
 | 		{ | 
 | 			case '\\': | 
 | 				fputs("\\(rs", fout); | 
 | 				break; | 
 | 			default: | 
 | 				fputc(*p, fout); | 
 | 		} | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | print_troff_ms_text(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	unsigned int i; | 
 | 	const char *const * ptr; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (opt_border > 2) | 
 | 		opt_border = 2; | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 		{ | 
 | 			fputs(".LP\n.DS C\n", fout); | 
 | 			troff_ms_escaped_print(cont->title, fout); | 
 | 			fputs("\n.DE\n", fout); | 
 | 		} | 
 |  | 
 | 		/* begin environment and set alignments and borders */ | 
 | 		fputs(".LP\n.TS\n", fout); | 
 | 		if (opt_border == 2) | 
 | 			fputs("center box;\n", fout); | 
 | 		else | 
 | 			fputs("center;\n", fout); | 
 |  | 
 | 		for (i = 0; i < cont->ncolumns; i++) | 
 | 		{ | 
 | 			fputc(*(cont->aligns + i), fout); | 
 | 			if (opt_border > 0 && i < cont->ncolumns - 1) | 
 | 				fputs(" | ", fout); | 
 | 		} | 
 | 		fputs(".\n", fout); | 
 |  | 
 | 		/* print headers */ | 
 | 		if (!opt_tuples_only) | 
 | 		{ | 
 | 			for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++) | 
 | 			{ | 
 | 				if (i != 0) | 
 | 					fputc('\t', fout); | 
 | 				fputs("\\fI", fout); | 
 | 				troff_ms_escaped_print(*ptr, fout); | 
 | 				fputs("\\fP", fout); | 
 | 			} | 
 | 			fputs("\n_\n", fout); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* print cells */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		troff_ms_escaped_print(*ptr, fout); | 
 |  | 
 | 		if ((i + 1) % cont->ncolumns == 0) | 
 | 		{ | 
 | 			fputc('\n', fout); | 
 | 			if (cancel_pressed) | 
 | 				break; | 
 | 		} | 
 | 		else | 
 | 			fputc('\t', fout); | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		fputs(".TE\n.DS L\n", fout); | 
 |  | 
 | 		/* print footers */ | 
 | 		if (cont->footers && !opt_tuples_only && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 			{ | 
 | 				troff_ms_escaped_print(f->data, fout); | 
 | 				fputc('\n', fout); | 
 | 			} | 
 | 		} | 
 |  | 
 | 		fputs(".DE\n", fout); | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | print_troff_ms_vertical(const printTableContent *cont, FILE *fout) | 
 | { | 
 | 	bool		opt_tuples_only = cont->opt->tuples_only; | 
 | 	unsigned short opt_border = cont->opt->border; | 
 | 	unsigned long record = cont->opt->prior_records + 1; | 
 | 	unsigned int i; | 
 | 	const char *const * ptr; | 
 | 	unsigned short current_format = 0;	/* 0=none, 1=header, 2=body */ | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (opt_border > 2) | 
 | 		opt_border = 2; | 
 |  | 
 | 	if (cont->opt->start_table) | 
 | 	{ | 
 | 		/* print title */ | 
 | 		if (!opt_tuples_only && cont->title) | 
 | 		{ | 
 | 			fputs(".LP\n.DS C\n", fout); | 
 | 			troff_ms_escaped_print(cont->title, fout); | 
 | 			fputs("\n.DE\n", fout); | 
 | 		} | 
 |  | 
 | 		/* begin environment and set alignments and borders */ | 
 | 		fputs(".LP\n.TS\n", fout); | 
 | 		if (opt_border == 2) | 
 | 			fputs("center box;\n", fout); | 
 | 		else | 
 | 			fputs("center;\n", fout); | 
 |  | 
 | 		/* basic format */ | 
 | 		if (opt_tuples_only) | 
 | 			fputs("c l;\n", fout); | 
 | 	} | 
 | 	else | 
 | 		current_format = 2;		/* assume tuples printed already */ | 
 |  | 
 | 	/* print records */ | 
 | 	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++) | 
 | 	{ | 
 | 		/* new record */ | 
 | 		if (i % cont->ncolumns == 0) | 
 | 		{ | 
 | 			if (cancel_pressed) | 
 | 				break; | 
 | 			if (!opt_tuples_only) | 
 | 			{ | 
 | 				if (current_format != 1) | 
 | 				{ | 
 | 					if (opt_border == 2 && record > 1) | 
 | 						fputs("_\n", fout); | 
 | 					if (current_format != 0) | 
 | 						fputs(".T&\n", fout); | 
 | 					fputs("c s.\n", fout); | 
 | 					current_format = 1; | 
 | 				} | 
 | 				fprintf(fout, "\\fIRecord %lu\\fP\n", record++); | 
 | 			} | 
 | 			if (opt_border >= 1) | 
 | 				fputs("_\n", fout); | 
 | 		} | 
 |  | 
 | 		if (!opt_tuples_only) | 
 | 		{ | 
 | 			if (current_format != 2) | 
 | 			{ | 
 | 				if (current_format != 0) | 
 | 					fputs(".T&\n", fout); | 
 | 				if (opt_border != 1) | 
 | 					fputs("c l.\n", fout); | 
 | 				else | 
 | 					fputs("c | l.\n", fout); | 
 | 				current_format = 2; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		troff_ms_escaped_print(cont->headers[i % cont->ncolumns], fout); | 
 | 		fputc('\t', fout); | 
 | 		troff_ms_escaped_print(*ptr, fout); | 
 |  | 
 | 		fputc('\n', fout); | 
 | 	} | 
 |  | 
 | 	if (cont->opt->stop_table) | 
 | 	{ | 
 | 		fputs(".TE\n.DS L\n", fout); | 
 |  | 
 | 		/* print footers */ | 
 | 		if (cont->footers && !opt_tuples_only && !cancel_pressed) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 			{ | 
 | 				troff_ms_escaped_print(f->data, fout); | 
 | 				fputc('\n', fout); | 
 | 			} | 
 | 		} | 
 |  | 
 | 		fputs(".DE\n", fout); | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | /********************************/ | 
 | /* Public functions		*/ | 
 | /********************************/ | 
 |  | 
 |  | 
 | /* | 
 |  * PageOutput | 
 |  * | 
 |  * Tests if pager is needed and returns appropriate FILE pointer. | 
 |  */ | 
 | FILE * | 
 | PageOutput(int lines, unsigned short int pager) | 
 | { | 
 | 	/* check whether we need / can / are supposed to use pager */ | 
 | 	if (pager && isatty(fileno(stdin)) && isatty(fileno(stdout))) | 
 | 	{ | 
 | 		const char *pagerprog; | 
 | 		FILE	   *pagerpipe; | 
 |  | 
 | #ifdef TIOCGWINSZ | 
 | 		int			result; | 
 | 		struct winsize screen_size; | 
 |  | 
 | 		result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size); | 
 |  | 
 | 		/* >= accounts for a one-line prompt */ | 
 | 		if (result == -1 || lines >= screen_size.ws_row || pager > 1) | 
 | 		{ | 
 | #endif | 
 | 			pagerprog = getenv("PAGER"); | 
 | 			if (!pagerprog) | 
 | 				pagerprog = DEFAULT_PAGER; | 
 | #ifndef WIN32 | 
 | 			pqsignal(SIGPIPE, SIG_IGN); | 
 | #endif | 
 | 			pagerpipe = popen(pagerprog, "w"); | 
 | 			if (pagerpipe) | 
 | 				return pagerpipe; | 
 | #ifdef TIOCGWINSZ | 
 | 		} | 
 | #endif | 
 | 	} | 
 |  | 
 | 	return stdout; | 
 | } | 
 |  | 
 | /* | 
 |  * ClosePager | 
 |  * | 
 |  * Close previously opened pager pipe, if any | 
 |  */ | 
 | void | 
 | ClosePager(FILE *pagerpipe) | 
 | { | 
 | 	if (pagerpipe && pagerpipe != stdout) | 
 | 	{ | 
 | 		/* | 
 | 		 * If printing was canceled midstream, warn about it. | 
 | 		 * | 
 | 		 * Some pagers like less use Ctrl-C as part of their command set. Even | 
 | 		 * so, we abort our processing and warn the user what we did.  If the | 
 | 		 * pager quit as a result of the SIGINT, this message won't go | 
 | 		 * anywhere ... | 
 | 		 */ | 
 | 		if (cancel_pressed) | 
 | 			fprintf(pagerpipe, _("Interrupted\n")); | 
 |  | 
 | 		pclose(pagerpipe); | 
 | #ifndef WIN32 | 
 | 		pqsignal(SIGPIPE, SIG_DFL); | 
 | #endif | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  * Initialise a table contents struct. | 
 |  *		Must be called before any other printTable method is used. | 
 |  * | 
 |  * The title is not duplicated; the caller must ensure that the buffer | 
 |  * is available for the lifetime of the printTableContent struct. | 
 |  * | 
 |  * If you call this, you must call printTableCleanup once you're done with the | 
 |  * table. | 
 |  */ | 
 | void | 
 | printTableInit(printTableContent *const content, printTableOpt *opt, | 
 | 			   const char *title, const int ncolumns, const int nrows) | 
 | { | 
 | 	content->opt = opt; | 
 | 	content->title = title; | 
 | 	content->ncolumns = ncolumns; | 
 | 	content->nrows = nrows; | 
 |  | 
 | 	content->headers = pg_local_calloc(ncolumns + 1, | 
 | 									   sizeof(*content->headers)); | 
 |  | 
 | 	content->cells = pg_local_calloc(ncolumns * nrows + 1, | 
 | 									 sizeof(*content->cells)); | 
 |  | 
 | 	content->cellmustfree = NULL; | 
 | 	content->footers = NULL; | 
 |  | 
 | 	content->aligns = pg_local_calloc(ncolumns + 1, | 
 | 									  sizeof(*content->align)); | 
 |  | 
 | 	content->header = content->headers; | 
 | 	content->cell = content->cells; | 
 | 	content->footer = content->footers; | 
 | 	content->align = content->aligns; | 
 | 	content->cellsadded = 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Add a header to the table. | 
 |  * | 
 |  * Headers are not duplicated; you must ensure that the header string is | 
 |  * available for the lifetime of the printTableContent struct. | 
 |  * | 
 |  * If translate is true, the function will pass the header through gettext. | 
 |  * Otherwise, the header will not be translated. | 
 |  * | 
 |  * align is either 'l' or 'r', and specifies the alignment for cells in this | 
 |  * column. | 
 |  */ | 
 | void | 
 | printTableAddHeader(printTableContent *const content, const char *header, | 
 | 					const bool translate, const char align) | 
 | { | 
 | #ifndef ENABLE_NLS | 
 | 	(void) translate;			/* unused parameter */ | 
 | #endif | 
 |  | 
 | 	if (content->header >= content->headers + content->ncolumns) | 
 | 	{ | 
 | 		fprintf(stderr, _("Cannot add header to table content: " | 
 | 						  "column count of %d exceeded.\n"), | 
 | 				content->ncolumns); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 |  | 
 | 	*content->header = (char *) mbvalidate((unsigned char *) header, | 
 | 										   content->opt->encoding); | 
 | #ifdef ENABLE_NLS | 
 | 	if (translate) | 
 | 		*content->header = _(*content->header); | 
 | #endif | 
 | 	content->header++; | 
 |  | 
 | 	*content->align = align; | 
 | 	content->align++; | 
 | } | 
 |  | 
 | /* | 
 |  * Add a cell to the table. | 
 |  * | 
 |  * Cells are not duplicated; you must ensure that the cell string is available | 
 |  * for the lifetime of the printTableContent struct. | 
 |  * | 
 |  * If translate is true, the function will pass the cell through gettext. | 
 |  * Otherwise, the cell will not be translated. | 
 |  * | 
 |  * If mustfree is true, the cell string is freed by printTableCleanup(). | 
 |  * Note: Automatic freeing of translatable strings is not supported. | 
 |  */ | 
 | void | 
 | printTableAddCell(printTableContent *const content, const char *cell, | 
 | 				  const bool translate, const bool mustfree) | 
 | { | 
 | #ifndef ENABLE_NLS | 
 | 	(void) translate;			/* unused parameter */ | 
 | #endif | 
 |  | 
 | 	if (content->cellsadded >= content->ncolumns * content->nrows) | 
 | 	{ | 
 | 		fprintf(stderr, _("Cannot add cell to table content: " | 
 | 						  "total cell count of %d exceeded.\n"), | 
 | 				content->ncolumns * content->nrows); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 |  | 
 | 	*content->cell = (char *) mbvalidate((unsigned char *) cell, | 
 | 										 content->opt->encoding); | 
 |  | 
 | #ifdef ENABLE_NLS | 
 | 	if (translate) | 
 | 		*content->cell = _(*content->cell); | 
 | #endif | 
 |  | 
 | 	if (mustfree) | 
 | 	{ | 
 | 		if (content->cellmustfree == NULL) | 
 | 			content->cellmustfree = pg_local_calloc( | 
 | 					   content->ncolumns * content->nrows + 1, sizeof(bool)); | 
 |  | 
 | 		content->cellmustfree[content->cellsadded] = true; | 
 | 	} | 
 | 	content->cell++; | 
 | 	content->cellsadded++; | 
 | } | 
 |  | 
 | /* | 
 |  * Add a footer to the table. | 
 |  * | 
 |  * Footers are added as elements of a singly-linked list, and the content is | 
 |  * strdup'd, so there is no need to keep the original footer string around. | 
 |  * | 
 |  * Footers are never translated by the function.  If you want the footer | 
 |  * translated you must do so yourself, before calling printTableAddFooter.	The | 
 |  * reason this works differently to headers and cells is that footers tend to | 
 |  * be made of up individually translated components, rather than being | 
 |  * translated as a whole. | 
 |  */ | 
 | void | 
 | printTableAddFooter(printTableContent *const content, const char *footer) | 
 | { | 
 | 	printTableFooter *f; | 
 |  | 
 | 	f = pg_local_calloc(1, sizeof(*f)); | 
 | 	f->data = pg_strdup(footer); | 
 |  | 
 | 	if (content->footers == NULL) | 
 | 		content->footers = f; | 
 | 	else | 
 | 		content->footer->next = f; | 
 |  | 
 | 	content->footer = f; | 
 | } | 
 |  | 
 | /* | 
 |  * Change the content of the last-added footer. | 
 |  * | 
 |  * The current contents of the last-added footer are freed, and replaced by the | 
 |  * content given in *footer.  If there was no previous footer, add a new one. | 
 |  * | 
 |  * The content is strdup'd, so there is no need to keep the original string | 
 |  * around. | 
 |  */ | 
 | void | 
 | printTableSetFooter(printTableContent *const content, const char *footer) | 
 | { | 
 | 	if (content->footers != NULL) | 
 | 	{ | 
 | 		free(content->footer->data); | 
 | 		content->footer->data = pg_strdup(footer); | 
 | 	} | 
 | 	else | 
 | 		printTableAddFooter(content, footer); | 
 | } | 
 |  | 
 | /* | 
 |  * Free all memory allocated to this struct. | 
 |  * | 
 |  * Once this has been called, the struct is unusable unless you pass it to | 
 |  * printTableInit() again. | 
 |  */ | 
 | void | 
 | printTableCleanup(printTableContent *const content) | 
 | { | 
 | 	if (content->cellmustfree) | 
 | 	{ | 
 | 		int			i; | 
 |  | 
 | 		for (i = 0; i < content->nrows * content->ncolumns; i++) | 
 | 		{ | 
 | 			if (content->cellmustfree[i]) | 
 | 				free((char *) content->cells[i]); | 
 | 		} | 
 | 		free(content->cellmustfree); | 
 | 		content->cellmustfree = NULL; | 
 | 	} | 
 | 	free(content->headers); | 
 | 	free(content->cells); | 
 | 	free(content->aligns); | 
 |  | 
 | 	content->opt = NULL; | 
 | 	content->title = NULL; | 
 | 	content->headers = NULL; | 
 | 	content->cells = NULL; | 
 | 	content->aligns = NULL; | 
 | 	content->header = NULL; | 
 | 	content->cell = NULL; | 
 | 	content->align = NULL; | 
 |  | 
 | 	if (content->footers) | 
 | 	{ | 
 | 		for (content->footer = content->footers; content->footer;) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			f = content->footer; | 
 | 			content->footer = f->next; | 
 | 			free(f->data); | 
 | 			free(f); | 
 | 		} | 
 | 	} | 
 | 	content->footers = NULL; | 
 | 	content->footer = NULL; | 
 | } | 
 |  | 
 | /* | 
 |  * IsPagerNeeded | 
 |  * | 
 |  * Setup pager if required | 
 |  */ | 
 | static void | 
 | IsPagerNeeded(const printTableContent *cont, const int extra_lines, FILE **fout, | 
 | 			  bool *is_pager) | 
 | { | 
 | 	if (*fout == stdout) | 
 | 	{ | 
 | 		int			lines; | 
 |  | 
 | 		if (cont->opt->expanded) | 
 | 			lines = (cont->ncolumns + 1) * cont->nrows; | 
 | 		else | 
 | 			lines = cont->nrows + 1; | 
 |  | 
 | 		if (!cont->opt->tuples_only) | 
 | 		{ | 
 | 			printTableFooter *f; | 
 |  | 
 | 			/* | 
 | 			 * FIXME -- this is slightly bogus: it counts the number of | 
 | 			 * footers, not the number of lines in them. | 
 | 			 */ | 
 | 			for (f = cont->footers; f; f = f->next) | 
 | 				lines++; | 
 | 		} | 
 |  | 
 | 		*fout = PageOutput(lines + extra_lines, cont->opt->pager); | 
 | 		*is_pager = (*fout != stdout); | 
 | 	} | 
 | 	else | 
 | 		*is_pager = false; | 
 | } | 
 |  | 
 | /* | 
 |  * Use this to print just any table in the supported formats. | 
 |  */ | 
 | void | 
 | printTable(const printTableContent *cont, FILE *fout, FILE *flog) | 
 | { | 
 | 	bool		is_pager = false; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	if (cont->opt->format == PRINT_NOTHING) | 
 | 		return; | 
 |  | 
 | 	/* print_aligned_text() handles the pager itself */ | 
 | 	if ((cont->opt->format != PRINT_ALIGNED && | 
 | 		 cont->opt->format != PRINT_WRAPPED) || | 
 | 		cont->opt->expanded) | 
 | 		IsPagerNeeded(cont, 0, &fout, &is_pager); | 
 |  | 
 | 	/* print the stuff */ | 
 |  | 
 | 	if (flog) | 
 | 		print_aligned_text(cont, flog); | 
 |  | 
 | 	switch (cont->opt->format) | 
 | 	{ | 
 | 		case PRINT_UNALIGNED: | 
 | 			if (cont->opt->expanded) | 
 | 				print_unaligned_vertical(cont, fout); | 
 | 			else | 
 | 				print_unaligned_text(cont, fout); | 
 | 			break; | 
 | 		case PRINT_ALIGNED: | 
 | 		case PRINT_WRAPPED: | 
 | 			if (cont->opt->expanded) | 
 | 				print_aligned_vertical(cont, fout); | 
 | 			else | 
 | 				print_aligned_text(cont, fout); | 
 | 			break; | 
 | 		case PRINT_HTML: | 
 | 			if (cont->opt->expanded) | 
 | 				print_html_vertical(cont, fout); | 
 | 			else | 
 | 				print_html_text(cont, fout); | 
 | 			break; | 
 | 		case PRINT_LATEX: | 
 | 			if (cont->opt->expanded) | 
 | 				print_latex_vertical(cont, fout); | 
 | 			else | 
 | 				print_latex_text(cont, fout); | 
 | 			break; | 
 | 		case PRINT_TROFF_MS: | 
 | 			if (cont->opt->expanded) | 
 | 				print_troff_ms_vertical(cont, fout); | 
 | 			else | 
 | 				print_troff_ms_text(cont, fout); | 
 | 			break; | 
 | 		default: | 
 | 			fprintf(stderr, _("invalid output format (internal error): %d"), | 
 | 					cont->opt->format); | 
 | 			exit(EXIT_FAILURE); | 
 | 	} | 
 |  | 
 | 	if (is_pager) | 
 | 		ClosePager(fout); | 
 | } | 
 |  | 
 | /* | 
 |  * Use this to print query results | 
 |  * | 
 |  * It calls printTable with all the things set straight. | 
 |  */ | 
 | void | 
 | printQuery(const PGresult *result, const printQueryOpt *opt, FILE *fout, FILE *flog) | 
 | { | 
 | 	printTableContent cont; | 
 | 	int			i, | 
 | 				r, | 
 | 				c; | 
 |  | 
 | 	if (cancel_pressed) | 
 | 		return; | 
 |  | 
 | 	printTableInit(&cont, (printTableOpt *) &opt->topt, opt->title, | 
 | 				   PQnfields(result), PQntuples(result)); | 
 |  | 
 | 	for (i = 0; i < cont.ncolumns; i++) | 
 | 	{ | 
 | 		char		align; | 
 | 		Oid			ftype = PQftype(result, i); | 
 |  | 
 | 		switch (ftype) | 
 | 		{ | 
 | 			case INT2OID: | 
 | 			case INT4OID: | 
 | 			case INT8OID: | 
 | 			case FLOAT4OID: | 
 | 			case FLOAT8OID: | 
 | 			case NUMERICOID: | 
 | 			case OIDOID: | 
 | 			case XIDOID: | 
 | 			case CIDOID: | 
 | 			case CASHOID: | 
 | 				align = 'r'; | 
 | 				break; | 
 | 			default: | 
 | 				align = 'l'; | 
 | 				break; | 
 | 		} | 
 |  | 
 | 		printTableAddHeader(&cont, PQfname(result, i), | 
 | 							opt->translate_header, align); | 
 | 	} | 
 |  | 
 | 	/* set cells */ | 
 | 	for (r = 0; r < cont.nrows; r++) | 
 | 	{ | 
 | 		for (c = 0; c < cont.ncolumns; c++) | 
 | 		{ | 
 | 			char	   *cell; | 
 | 			bool		mustfree = false; | 
 | 			bool		translate; | 
 |  | 
 | 			if (PQgetisnull(result, r, c)) | 
 | 				cell = opt->nullPrint ? opt->nullPrint : ""; | 
 | 			else | 
 | 			{ | 
 | 				cell = PQgetvalue(result, r, c); | 
 | 				if (cont.aligns[c] == 'r' && opt->topt.numericLocale) | 
 | 				{ | 
 | 					cell = format_numeric_locale(cell); | 
 | 					mustfree = true; | 
 | 				} | 
 | 			} | 
 |  | 
 | 			translate = (opt->translate_columns && opt->translate_columns[c]); | 
 | 			printTableAddCell(&cont, cell, translate, mustfree); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* set footers */ | 
 | 	if (opt->footers) | 
 | 	{ | 
 | 		char	  **footer; | 
 |  | 
 | 		for (footer = opt->footers; *footer; footer++) | 
 | 			printTableAddFooter(&cont, *footer); | 
 | 	} | 
 | 	else if (!opt->topt.expanded && opt->default_footer) | 
 | 	{ | 
 | 		unsigned long total_records; | 
 | 		char		default_footer[100]; | 
 |  | 
 | 		total_records = opt->topt.prior_records + cont.nrows; | 
 | 		snprintf(default_footer, sizeof(default_footer), | 
 | 				 ngettext("(%lu row)", "(%lu rows)", total_records), | 
 | 				 total_records); | 
 |  | 
 | 		printTableAddFooter(&cont, default_footer); | 
 | 	} | 
 |  | 
 | 	printTable(&cont, fout, flog); | 
 | 	printTableCleanup(&cont); | 
 | } | 
 |  | 
 |  | 
 | void | 
 | setDecimalLocale(void) | 
 | { | 
 | 	struct lconv *extlconv; | 
 |  | 
 | 	extlconv = localeconv(); | 
 |  | 
 | 	if (*extlconv->decimal_point) | 
 | 		decimal_point = pg_strdup(extlconv->decimal_point); | 
 | 	else | 
 | 		decimal_point = ".";	/* SQL output standard */ | 
 | 	if (*extlconv->grouping && atoi(extlconv->grouping) > 0) | 
 | 		grouping = pg_strdup(extlconv->grouping); | 
 | 	else | 
 | 		grouping = "3";			/* most common */ | 
 |  | 
 | 	/* similar code exists in formatting.c */ | 
 | 	if (*extlconv->thousands_sep) | 
 | 		thousands_sep = pg_strdup(extlconv->thousands_sep); | 
 | 	/* Make sure thousands separator doesn't match decimal point symbol. */ | 
 | 	else if (strcmp(decimal_point, ",") != 0) | 
 | 		thousands_sep = ","; | 
 | 	else | 
 | 		thousands_sep = "."; | 
 | } | 
 |  | 
 | /* get selected or default line style */ | 
 | const printTextFormat * | 
 | get_line_style(const printTableOpt *opt) | 
 | { | 
 | 	/* | 
 | 	 * Note: this function mainly exists to preserve the convention that a | 
 | 	 * printTableOpt struct can be initialized to zeroes to get default | 
 | 	 * behavior. | 
 | 	 */ | 
 | 	if (opt->line_style != NULL) | 
 | 		return opt->line_style; | 
 | 	else | 
 | 		return &pg_asciiformat_old;   /* GPDB: Use old-ascii format for regression test compatibility */ | 
 | } | 
 |  | 
 | /* | 
 |  * Compute the byte distance to the end of the string or *target_width | 
 |  * display character positions, whichever comes first.	Update *target_width | 
 |  * to be the number of display character positions actually filled. | 
 |  */ | 
 | static int | 
 | strlen_max_width(unsigned char *str, int *target_width, int encoding) | 
 | { | 
 | 	unsigned char *start = str; | 
 | 	unsigned char *end = str + strlen((char *) str); | 
 | 	int			curr_width = 0; | 
 |  | 
 | 	while (str < end) | 
 | 	{ | 
 | 		int			char_width = PQdsplen((char *) str, encoding); | 
 |  | 
 | 		/* | 
 | 		 * If the display width of the new character causes the string to | 
 | 		 * exceed its target width, skip it and return.  However, if this is | 
 | 		 * the first character of the string (curr_width == 0), we have to | 
 | 		 * accept it. | 
 | 		 */ | 
 | 		if (*target_width < curr_width + char_width && curr_width != 0) | 
 | 			break; | 
 |  | 
 | 		curr_width += char_width; | 
 |  | 
 | 		str += PQmblen((char *) str, encoding); | 
 | 	} | 
 |  | 
 | 	*target_width = curr_width; | 
 |  | 
 | 	return str - start; | 
 | } |