| /* |
| * 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; |
| } |