LCOV - code coverage report
Current view: top level - src/fe_utils - print.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 1450 1559 93.0 %
Date: 2020-05-25 05:06:35 Functions: 47 48 97.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * Query-result printing support for frontend code
       4             :  *
       5             :  * This file used to be part of psql, but now it's separated out to allow
       6             :  * other frontend programs to use it.  Because the printing code needs
       7             :  * access to the cancel_pressed flag as well as SIGPIPE trapping and
       8             :  * pager open/close functions, all that stuff came with it.
       9             :  *
      10             :  *
      11             :  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
      12             :  * Portions Copyright (c) 1994, Regents of the University of California
      13             :  *
      14             :  * src/fe_utils/print.c
      15             :  *
      16             :  *-------------------------------------------------------------------------
      17             :  */
      18             : #include "postgres_fe.h"
      19             : 
      20             : #include <limits.h>
      21             : #include <math.h>
      22             : #include <unistd.h>
      23             : 
      24             : #ifndef WIN32
      25             : #include <sys/ioctl.h>            /* for ioctl() */
      26             : #endif
      27             : 
      28             : #ifdef HAVE_TERMIOS_H
      29             : #include <termios.h>
      30             : #endif
      31             : 
      32             : #include "catalog/pg_type_d.h"
      33             : #include "fe_utils/mbprint.h"
      34             : #include "fe_utils/print.h"
      35             : 
      36             : /*
      37             :  * If the calling program doesn't have any mechanism for setting
      38             :  * cancel_pressed, it will have no effect.
      39             :  *
      40             :  * Note: print.c's general strategy for when to check cancel_pressed is to do
      41             :  * so at completion of each row of output.
      42             :  */
      43             : volatile sig_atomic_t cancel_pressed = false;
      44             : 
      45             : static bool always_ignore_sigpipe = false;
      46             : 
      47             : /* info for locale-aware numeric formatting; set up by setDecimalLocale() */
      48             : static char *decimal_point;
      49             : static int  groupdigits;
      50             : static char *thousands_sep;
      51             : 
      52             : static char default_footer[100];
      53             : static printTableFooter default_footer_cell = {default_footer, NULL};
      54             : 
      55             : /* Line style control structures */
      56             : const printTextFormat pg_asciiformat =
      57             : {
      58             :     "ascii",
      59             :     {
      60             :         {"-", "+", "+", "+"},
      61             :         {"-", "+", "+", "+"},
      62             :         {"-", "+", "+", "+"},
      63             :         {"", "|", "|", "|"}
      64             :     },
      65             :     "|",
      66             :     "|",
      67             :     "|",
      68             :     " ",
      69             :     "+",
      70             :     " ",
      71             :     "+",
      72             :     ".",
      73             :     ".",
      74             :     true
      75             : };
      76             : 
      77             : const printTextFormat pg_asciiformat_old =
      78             : {
      79             :     "old-ascii",
      80             :     {
      81             :         {"-", "+", "+", "+"},
      82             :         {"-", "+", "+", "+"},
      83             :         {"-", "+", "+", "+"},
      84             :         {"", "|", "|", "|"}
      85             :     },
      86             :     ":",
      87             :     ";",
      88             :     " ",
      89             :     "+",
      90             :     " ",
      91             :     " ",
      92             :     " ",
      93             :     " ",
      94             :     " ",
      95             :     false
      96             : };
      97             : 
      98             : /* Default unicode linestyle format */
      99             : printTextFormat pg_utf8format;
     100             : 
     101             : typedef struct unicodeStyleRowFormat
     102             : {
     103             :     const char *horizontal;
     104             :     const char *vertical_and_right[2];
     105             :     const char *vertical_and_left[2];
     106             : } unicodeStyleRowFormat;
     107             : 
     108             : typedef struct unicodeStyleColumnFormat
     109             : {
     110             :     const char *vertical;
     111             :     const char *vertical_and_horizontal[2];
     112             :     const char *up_and_horizontal[2];
     113             :     const char *down_and_horizontal[2];
     114             : } unicodeStyleColumnFormat;
     115             : 
     116             : typedef struct unicodeStyleBorderFormat
     117             : {
     118             :     const char *up_and_right;
     119             :     const char *vertical;
     120             :     const char *down_and_right;
     121             :     const char *horizontal;
     122             :     const char *down_and_left;
     123             :     const char *left_and_right;
     124             : } unicodeStyleBorderFormat;
     125             : 
     126             : typedef struct unicodeStyleFormat
     127             : {
     128             :     unicodeStyleRowFormat row_style[2];
     129             :     unicodeStyleColumnFormat column_style[2];
     130             :     unicodeStyleBorderFormat border_style[2];
     131             :     const char *header_nl_left;
     132             :     const char *header_nl_right;
     133             :     const char *nl_left;
     134             :     const char *nl_right;
     135             :     const char *wrap_left;
     136             :     const char *wrap_right;
     137             :     bool        wrap_right_border;
     138             : } unicodeStyleFormat;
     139             : 
     140             : static const unicodeStyleFormat unicode_style = {
     141             :     {
     142             :         {
     143             :             /* ─ */
     144             :             "\342\224\200",
     145             :             /* ├╟ */
     146             :             {"\342\224\234", "\342\225\237"},
     147             :             /* ┤╢ */
     148             :             {"\342\224\244", "\342\225\242"},
     149             :         },
     150             :         {
     151             :             /* ═ */
     152             :             "\342\225\220",
     153             :             /* ╞╠ */
     154             :             {"\342\225\236", "\342\225\240"},
     155             :             /* ╡╣ */
     156             :             {"\342\225\241", "\342\225\243"},
     157             :         },
     158             :     },
     159             :     {
     160             :         {
     161             :             /* │ */
     162             :             "\342\224\202",
     163             :             /* ┼╪ */
     164             :             {"\342\224\274", "\342\225\252"},
     165             :             /* ┴╧ */
     166             :             {"\342\224\264", "\342\225\247"},
     167             :             /* ┬╤ */
     168             :             {"\342\224\254", "\342\225\244"},
     169             :         },
     170             :         {
     171             :             /* ║ */
     172             :             "\342\225\221",
     173             :             /* ╫╬ */
     174             :             {"\342\225\253", "\342\225\254"},
     175             :             /* ╨╩ */
     176             :             {"\342\225\250", "\342\225\251"},
     177             :             /* ╥╦ */
     178             :             {"\342\225\245", "\342\225\246"},
     179             :         },
     180             :     },
     181             :     {
     182             :         /* └│┌─┐┘ */
     183             :         {"\342\224\224", "\342\224\202", "\342\224\214", "\342\224\200", "\342\224\220", "\342\224\230"},
     184             :         /* ╚║╔═╗╝ */
     185             :         {"\342\225\232", "\342\225\221", "\342\225\224", "\342\225\220", "\342\225\227", "\342\225\235"},
     186             :     },
     187             :     " ",
     188             :     "\342\206\265",               /* ↵ */
     189             :     " ",
     190             :     "\342\206\265",               /* ↵ */
     191             :     "\342\200\246",               /* … */
     192             :     "\342\200\246",               /* … */
     193             :     true
     194             : };
     195             : 
     196             : 
     197             : /* Local functions */
     198             : static int  strlen_max_width(unsigned char *str, int *target_width, int encoding);
     199             : static void IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
     200             :                           FILE **fout, bool *is_pager);
     201             : 
     202             : static void print_aligned_vertical(const printTableContent *cont,
     203             :                                    FILE *fout, bool is_pager);
     204             : 
     205             : 
     206             : /* Count number of digits in integral part of number */
     207             : static int
     208         128 : integer_digits(const char *my_str)
     209             : {
     210             :     /* ignoring any sign ... */
     211         128 :     if (my_str[0] == '-' || my_str[0] == '+')
     212          24 :         my_str++;
     213             :     /* ... count initial integral digits */
     214         128 :     return strspn(my_str, "0123456789");
     215             : }
     216             : 
     217             : /* Compute additional length required for locale-aware numeric output */
     218             : static int
     219          64 : additional_numeric_locale_len(const char *my_str)
     220             : {
     221          64 :     int         int_len = integer_digits(my_str),
     222          64 :                 len = 0;
     223             : 
     224             :     /* Account for added thousands_sep instances */
     225          64 :     if (int_len > groupdigits)
     226           0 :         len += ((int_len - 1) / groupdigits) * strlen(thousands_sep);
     227             : 
     228             :     /* Account for possible additional length of decimal_point */
     229          64 :     if (strchr(my_str, '.') != NULL)
     230           0 :         len += strlen(decimal_point) - 1;
     231             : 
     232          64 :     return len;
     233             : }
     234             : 
     235             : /*
     236             :  * Format a numeric value per current LC_NUMERIC locale setting
     237             :  *
     238             :  * Returns the appropriately formatted string in a new allocated block,
     239             :  * caller must free.
     240             :  *
     241             :  * setDecimalLocale() must have been called earlier.
     242             :  */
     243             : static char *
     244          64 : format_numeric_locale(const char *my_str)
     245             : {
     246             :     char       *new_str;
     247             :     int         new_len,
     248             :                 int_len,
     249             :                 leading_digits,
     250             :                 i,
     251             :                 new_str_pos;
     252             : 
     253             :     /*
     254             :      * If the string doesn't look like a number, return it unchanged.  This
     255             :      * check is essential to avoid mangling already-localized "money" values.
     256             :      */
     257          64 :     if (strspn(my_str, "0123456789+-.eE") != strlen(my_str))
     258           0 :         return pg_strdup(my_str);
     259             : 
     260          64 :     new_len = strlen(my_str) + additional_numeric_locale_len(my_str);
     261          64 :     new_str = pg_malloc(new_len + 1);
     262          64 :     new_str_pos = 0;
     263          64 :     int_len = integer_digits(my_str);
     264             : 
     265             :     /* number of digits in first thousands group */
     266          64 :     leading_digits = int_len % groupdigits;
     267          64 :     if (leading_digits == 0)
     268          12 :         leading_digits = groupdigits;
     269             : 
     270             :     /* process sign */
     271          64 :     if (my_str[0] == '-' || my_str[0] == '+')
     272             :     {
     273          12 :         new_str[new_str_pos++] = my_str[0];
     274          12 :         my_str++;
     275             :     }
     276             : 
     277             :     /* process integer part of number */
     278         152 :     for (i = 0; i < int_len; i++)
     279             :     {
     280             :         /* Time to insert separator? */
     281          88 :         if (i > 0 && --leading_digits == 0)
     282             :         {
     283           0 :             strcpy(&new_str[new_str_pos], thousands_sep);
     284           0 :             new_str_pos += strlen(thousands_sep);
     285           0 :             leading_digits = groupdigits;
     286             :         }
     287          88 :         new_str[new_str_pos++] = my_str[i];
     288             :     }
     289             : 
     290             :     /* handle decimal point if any */
     291          64 :     if (my_str[i] == '.')
     292             :     {
     293           0 :         strcpy(&new_str[new_str_pos], decimal_point);
     294           0 :         new_str_pos += strlen(decimal_point);
     295           0 :         i++;
     296             :     }
     297             : 
     298             :     /* copy the rest (fractional digits and/or exponent, and \0 terminator) */
     299          64 :     strcpy(&new_str[new_str_pos], &my_str[i]);
     300             : 
     301             :     /* assert we didn't underestimate new_len (an overestimate is OK) */
     302             :     Assert(strlen(new_str) <= new_len);
     303             : 
     304          64 :     return new_str;
     305             : }
     306             : 
     307             : 
     308             : /*
     309             :  * fputnbytes: print exactly N bytes to a file
     310             :  *
     311             :  * We avoid using %.*s here because it can misbehave if the data
     312             :  * is not valid in what libc thinks is the prevailing encoding.
     313             :  */
     314             : static void
     315      629322 : fputnbytes(FILE *f, const char *str, size_t n)
     316             : {
     317     7478824 :     while (n-- > 0)
     318     6849502 :         fputc(*str++, f);
     319      629322 : }
     320             : 
     321             : 
     322             : static void
     323       13746 : print_separator(struct separator sep, FILE *fout)
     324             : {
     325       13746 :     if (sep.separator_zero)
     326           0 :         fputc('\000', fout);
     327       13746 :     else if (sep.separator)
     328       13746 :         fputs(sep.separator, fout);
     329       13746 : }
     330             : 
     331             : 
     332             : /*
     333             :  * Return the list of explicitly-requested footers or, when applicable, the
     334             :  * default "(xx rows)" footer.  Always omit the default footer when given
     335             :  * non-default footers, "\pset footer off", or a specific instruction to that
     336             :  * effect from a calling backslash command.  Vertical formats number each row,
     337             :  * making the default footer redundant; they do not call this function.
     338             :  *
     339             :  * The return value may point to static storage; do not keep it across calls.
     340             :  */
     341             : static printTableFooter *
     342       62588 : footers_with_default(const printTableContent *cont)
     343             : {
     344       62588 :     if (cont->footers == NULL && cont->opt->default_footer)
     345             :     {
     346             :         unsigned long total_records;
     347             : 
     348       60796 :         total_records = cont->opt->prior_records + cont->nrows;
     349       60796 :         snprintf(default_footer, sizeof(default_footer),
     350       60796 :                  ngettext("(%lu row)", "(%lu rows)", total_records),
     351             :                  total_records);
     352             : 
     353       60796 :         return &default_footer_cell;
     354             :     }
     355             :     else
     356        1792 :         return cont->footers;
     357             : }
     358             : 
     359             : 
     360             : /*************************/
     361             : /* Unaligned text        */
     362             : /*************************/
     363             : 
     364             : 
     365             : static void
     366        1664 : print_unaligned_text(const printTableContent *cont, FILE *fout)
     367             : {
     368        1664 :     bool        opt_tuples_only = cont->opt->tuples_only;
     369             :     unsigned int i;
     370             :     const char *const *ptr;
     371        1664 :     bool        need_recordsep = false;
     372             : 
     373        1664 :     if (cancel_pressed)
     374           0 :         return;
     375             : 
     376        1664 :     if (cont->opt->start_table)
     377             :     {
     378             :         /* print title */
     379        1664 :         if (!opt_tuples_only && cont->title)
     380             :         {
     381           4 :             fputs(cont->title, fout);
     382           4 :             print_separator(cont->opt->recordSep, fout);
     383             :         }
     384             : 
     385             :         /* print headers */
     386        1664 :         if (!opt_tuples_only)
     387             :         {
     388         216 :             for (ptr = cont->headers; *ptr; ptr++)
     389             :             {
     390         160 :                 if (ptr != cont->headers)
     391         104 :                     print_separator(cont->opt->fieldSep, fout);
     392         160 :                 fputs(*ptr, fout);
     393             :             }
     394          56 :             need_recordsep = true;
     395             :         }
     396             :     }
     397             :     else
     398             :         /* assume continuing printout */
     399           0 :         need_recordsep = true;
     400             : 
     401             :     /* print cells */
     402       14704 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
     403             :     {
     404       13040 :         if (need_recordsep)
     405             :         {
     406        5820 :             print_separator(cont->opt->recordSep, fout);
     407        5820 :             need_recordsep = false;
     408        5820 :             if (cancel_pressed)
     409           0 :                 break;
     410             :         }
     411       13040 :         fputs(*ptr, fout);
     412             : 
     413       13040 :         if ((i + 1) % cont->ncolumns)
     414        5694 :             print_separator(cont->opt->fieldSep, fout);
     415             :         else
     416        7346 :             need_recordsep = true;
     417             :     }
     418             : 
     419             :     /* print footers */
     420        1664 :     if (cont->opt->stop_table)
     421             :     {
     422        1664 :         printTableFooter *footers = footers_with_default(cont);
     423             : 
     424        1664 :         if (!opt_tuples_only && footers != NULL && !cancel_pressed)
     425             :         {
     426             :             printTableFooter *f;
     427             : 
     428         112 :             for (f = footers; f; f = f->next)
     429             :             {
     430          56 :                 if (need_recordsep)
     431             :                 {
     432          56 :                     print_separator(cont->opt->recordSep, fout);
     433          56 :                     need_recordsep = false;
     434             :                 }
     435          56 :                 fputs(f->data, fout);
     436          56 :                 need_recordsep = true;
     437             :             }
     438             :         }
     439             : 
     440             :         /*
     441             :          * The last record is terminated by a newline, independent of the set
     442             :          * record separator.  But when the record separator is a zero byte, we
     443             :          * use that (compatible with find -print0 and xargs).
     444             :          */
     445        1664 :         if (need_recordsep)
     446             :         {
     447        1582 :             if (cont->opt->recordSep.separator_zero)
     448           0 :                 print_separator(cont->opt->recordSep, fout);
     449             :             else
     450        1582 :                 fputc('\n', fout);
     451             :         }
     452             :     }
     453             : }
     454             : 
     455             : 
     456             : static void
     457          68 : print_unaligned_vertical(const printTableContent *cont, FILE *fout)
     458             : {
     459          68 :     bool        opt_tuples_only = cont->opt->tuples_only;
     460             :     unsigned int i;
     461             :     const char *const *ptr;
     462          68 :     bool        need_recordsep = false;
     463             : 
     464          68 :     if (cancel_pressed)
     465           0 :         return;
     466             : 
     467          68 :     if (cont->opt->start_table)
     468             :     {
     469             :         /* print title */
     470          68 :         if (!opt_tuples_only && cont->title)
     471             :         {
     472           4 :             fputs(cont->title, fout);
     473           4 :             need_recordsep = true;
     474             :         }
     475             :     }
     476             :     else
     477             :         /* assume continuing printout */
     478           0 :         need_recordsep = true;
     479             : 
     480             :     /* print records */
     481         952 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
     482             :     {
     483         884 :         if (need_recordsep)
     484             :         {
     485             :             /* record separator is 2 occurrences of recordsep in this mode */
     486         356 :             print_separator(cont->opt->recordSep, fout);
     487         356 :             print_separator(cont->opt->recordSep, fout);
     488         356 :             need_recordsep = false;
     489         356 :             if (cancel_pressed)
     490           0 :                 break;
     491             :         }
     492             : 
     493         884 :         fputs(cont->headers[i % cont->ncolumns], fout);
     494         884 :         print_separator(cont->opt->fieldSep, fout);
     495         884 :         fputs(*ptr, fout);
     496             : 
     497         884 :         if ((i + 1) % cont->ncolumns)
     498         464 :             print_separator(cont->opt->recordSep, fout);
     499             :         else
     500         420 :             need_recordsep = true;
     501             :     }
     502             : 
     503          68 :     if (cont->opt->stop_table)
     504             :     {
     505             :         /* print footers */
     506          68 :         if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
     507             :         {
     508             :             printTableFooter *f;
     509             : 
     510           4 :             print_separator(cont->opt->recordSep, fout);
     511           8 :             for (f = cont->footers; f; f = f->next)
     512             :             {
     513           4 :                 print_separator(cont->opt->recordSep, fout);
     514           4 :                 fputs(f->data, fout);
     515             :             }
     516             :         }
     517             : 
     518             :         /* see above in print_unaligned_text() */
     519          68 :         if (need_recordsep)
     520             :         {
     521          68 :             if (cont->opt->recordSep.separator_zero)
     522           0 :                 print_separator(cont->opt->recordSep, fout);
     523             :             else
     524          68 :                 fputc('\n', fout);
     525             :         }
     526             :     }
     527             : }
     528             : 
     529             : 
     530             : /********************/
     531             : /* Aligned text     */
     532             : /********************/
     533             : 
     534             : 
     535             : /* draw "line" */
     536             : static void
     537       60880 : _print_horizontal_line(const unsigned int ncolumns, const unsigned int *widths,
     538             :                        unsigned short border, printTextRule pos,
     539             :                        const printTextFormat *format,
     540             :                        FILE *fout)
     541             : {
     542       60880 :     const printTextLineFormat *lformat = &format->lrule[pos];
     543             :     unsigned int i,
     544             :                 j;
     545             : 
     546       60880 :     if (border == 1)
     547       60752 :         fputs(lformat->hrule, fout);
     548         128 :     else if (border == 2)
     549          96 :         fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
     550             : 
     551      175896 :     for (i = 0; i < ncolumns; i++)
     552             :     {
     553     1923666 :         for (j = 0; j < widths[i]; j++)
     554     1808650 :             fputs(lformat->hrule, fout);
     555             : 
     556      115016 :         if (i < ncolumns - 1)
     557             :         {
     558       54204 :             if (border == 0)
     559          32 :                 fputc(' ', fout);
     560             :             else
     561       54172 :                 fprintf(fout, "%s%s%s", lformat->hrule,
     562             :                         lformat->midvrule, lformat->hrule);
     563             :         }
     564             :     }
     565             : 
     566       60880 :     if (border == 2)
     567          96 :         fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
     568       60784 :     else if (border == 1)
     569       60752 :         fputs(lformat->hrule, fout);
     570             : 
     571       60880 :     fputc('\n', fout);
     572       60880 : }
     573             : 
     574             : 
     575             : /*
     576             :  *  Print pretty boxes around cells.
     577             :  */
     578             : static void
     579       60868 : print_aligned_text(const printTableContent *cont, FILE *fout, bool is_pager)
     580             : {
     581       60868 :     bool        opt_tuples_only = cont->opt->tuples_only;
     582       60868 :     int         encoding = cont->opt->encoding;
     583       60868 :     unsigned short opt_border = cont->opt->border;
     584       60868 :     const printTextFormat *format = get_line_style(cont->opt);
     585       60868 :     const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
     586             : 
     587       60868 :     unsigned int col_count = 0,
     588       60868 :                 cell_count = 0;
     589             : 
     590             :     unsigned int i,
     591             :                 j;
     592             : 
     593             :     unsigned int *width_header,
     594             :                *max_width,
     595             :                *width_wrap,
     596             :                *width_average;
     597             :     unsigned int *max_nl_lines, /* value split by newlines */
     598             :                *curr_nl_line,
     599             :                *max_bytes;
     600             :     unsigned char **format_buf;
     601             :     unsigned int width_total;
     602             :     unsigned int total_header_width;
     603       60868 :     unsigned int extra_row_output_lines = 0;
     604       60868 :     unsigned int extra_output_lines = 0;
     605             : 
     606             :     const char *const *ptr;
     607             : 
     608             :     struct lineptr **col_lineptrs;  /* pointers to line pointer per column */
     609             : 
     610             :     bool       *header_done;    /* Have all header lines been output? */
     611             :     int        *bytes_output;   /* Bytes output for column value */
     612             :     printTextLineWrap *wrap;    /* Wrap status for each column */
     613       60868 :     int         output_columns = 0; /* Width of interactive console */
     614       60868 :     bool        is_local_pager = false;
     615             : 
     616       60868 :     if (cancel_pressed)
     617           0 :         return;
     618             : 
     619       60868 :     if (opt_border > 2)
     620           0 :         opt_border = 2;
     621             : 
     622       60868 :     if (cont->ncolumns > 0)
     623             :     {
     624       60800 :         col_count = cont->ncolumns;
     625       60800 :         width_header = pg_malloc0(col_count * sizeof(*width_header));
     626       60800 :         width_average = pg_malloc0(col_count * sizeof(*width_average));
     627       60800 :         max_width = pg_malloc0(col_count * sizeof(*max_width));
     628       60800 :         width_wrap = pg_malloc0(col_count * sizeof(*width_wrap));
     629       60800 :         max_nl_lines = pg_malloc0(col_count * sizeof(*max_nl_lines));
     630       60800 :         curr_nl_line = pg_malloc0(col_count * sizeof(*curr_nl_line));
     631       60800 :         col_lineptrs = pg_malloc0(col_count * sizeof(*col_lineptrs));
     632       60800 :         max_bytes = pg_malloc0(col_count * sizeof(*max_bytes));
     633       60800 :         format_buf = pg_malloc0(col_count * sizeof(*format_buf));
     634       60800 :         header_done = pg_malloc0(col_count * sizeof(*header_done));
     635       60800 :         bytes_output = pg_malloc0(col_count * sizeof(*bytes_output));
     636       60800 :         wrap = pg_malloc0(col_count * sizeof(*wrap));
     637             :     }
     638             :     else
     639             :     {
     640          68 :         width_header = NULL;
     641          68 :         width_average = NULL;
     642          68 :         max_width = NULL;
     643          68 :         width_wrap = NULL;
     644          68 :         max_nl_lines = NULL;
     645          68 :         curr_nl_line = NULL;
     646          68 :         col_lineptrs = NULL;
     647          68 :         max_bytes = NULL;
     648          68 :         format_buf = NULL;
     649          68 :         header_done = NULL;
     650          68 :         bytes_output = NULL;
     651          68 :         wrap = NULL;
     652             :     }
     653             : 
     654             :     /* scan all column headers, find maximum width and max max_nl_lines */
     655      175864 :     for (i = 0; i < col_count; i++)
     656             :     {
     657             :         int         width,
     658             :                     nl_lines,
     659             :                     bytes_required;
     660             : 
     661      114996 :         pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
     662             :                    encoding, &width, &nl_lines, &bytes_required);
     663      114996 :         if (width > max_width[i])
     664      114972 :             max_width[i] = width;
     665      114996 :         if (nl_lines > max_nl_lines[i])
     666      114996 :             max_nl_lines[i] = nl_lines;
     667      114996 :         if (bytes_required > max_bytes[i])
     668      114996 :             max_bytes[i] = bytes_required;
     669      114996 :         if (nl_lines > extra_row_output_lines)
     670       60800 :             extra_row_output_lines = nl_lines;
     671             : 
     672      114996 :         width_header[i] = width;
     673             :     }
     674             :     /* Add height of tallest header column */
     675       60868 :     extra_output_lines += extra_row_output_lines;
     676       60868 :     extra_row_output_lines = 0;
     677             : 
     678             :     /* scan all cells, find maximum width, compute cell_count */
     679      675318 :     for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++)
     680             :     {
     681             :         int         width,
     682             :                     nl_lines,
     683             :                     bytes_required;
     684             : 
     685      614450 :         pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
     686             :                    &width, &nl_lines, &bytes_required);
     687             : 
     688      614450 :         if (width > max_width[i % col_count])
     689       65110 :             max_width[i % col_count] = width;
     690      614450 :         if (nl_lines > max_nl_lines[i % col_count])
     691         884 :             max_nl_lines[i % col_count] = nl_lines;
     692      614450 :         if (bytes_required > max_bytes[i % col_count])
     693       65196 :             max_bytes[i % col_count] = bytes_required;
     694             : 
     695      614450 :         width_average[i % col_count] += width;
     696             :     }
     697             : 
     698             :     /* If we have rows, compute average */
     699       60868 :     if (col_count != 0 && cell_count != 0)
     700             :     {
     701       57156 :         int         rows = cell_count / col_count;
     702             : 
     703      163296 :         for (i = 0; i < col_count; i++)
     704      106140 :             width_average[i] /= rows;
     705             :     }
     706             : 
     707             :     /* adjust the total display width based on border style */
     708       60868 :     if (opt_border == 0)
     709          32 :         width_total = col_count;
     710       60836 :     else if (opt_border == 1)
     711       60804 :         width_total = col_count * 3 - ((col_count > 0) ? 1 : 0);
     712             :     else
     713          32 :         width_total = col_count * 3 + 1;
     714       60868 :     total_header_width = width_total;
     715             : 
     716      175864 :     for (i = 0; i < col_count; i++)
     717             :     {
     718      114996 :         width_total += max_width[i];
     719      114996 :         total_header_width += width_header[i];
     720             :     }
     721             : 
     722             :     /*
     723             :      * At this point: max_width[] contains the max width of each column,
     724             :      * max_nl_lines[] contains the max number of lines in each column,
     725             :      * max_bytes[] contains the maximum storage space for formatting strings,
     726             :      * width_total contains the giant width sum.  Now we allocate some memory
     727             :      * for line pointers.
     728             :      */
     729      175864 :     for (i = 0; i < col_count; i++)
     730             :     {
     731             :         /* Add entry for ptr == NULL array termination */
     732      114996 :         col_lineptrs[i] = pg_malloc0((max_nl_lines[i] + 1) *
     733             :                                      sizeof(**col_lineptrs));
     734             : 
     735      114996 :         format_buf[i] = pg_malloc(max_bytes[i] + 1);
     736             : 
     737      114996 :         col_lineptrs[i]->ptr = format_buf[i];
     738             :     }
     739             : 
     740             :     /* Default word wrap to the full width, i.e. no word wrap */
     741      175864 :     for (i = 0; i < col_count; i++)
     742      114996 :         width_wrap[i] = max_width[i];
     743             : 
     744             :     /*
     745             :      * Choose target output width: \pset columns, or $COLUMNS, or ioctl
     746             :      */
     747       60868 :     if (cont->opt->columns > 0)
     748         300 :         output_columns = cont->opt->columns;
     749       60568 :     else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
     750             :     {
     751          52 :         if (cont->opt->env_columns > 0)
     752           0 :             output_columns = cont->opt->env_columns;
     753             : #ifdef TIOCGWINSZ
     754             :         else
     755             :         {
     756             :             struct winsize screen_size;
     757             : 
     758          52 :             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
     759           0 :                 output_columns = screen_size.ws_col;
     760             :         }
     761             : #endif
     762             :     }
     763             : 
     764       60868 :     if (cont->opt->format == PRINT_WRAPPED)
     765             :     {
     766             :         /*
     767             :          * Optional optimized word wrap. Shrink columns with a high max/avg
     768             :          * ratio.  Slightly bias against wider columns. (Increases chance a
     769             :          * narrow column will fit in its cell.)  If available columns is
     770             :          * positive...  and greater than the width of the unshrinkable column
     771             :          * headers
     772             :          */
     773          72 :         if (output_columns > 0 && output_columns >= total_header_width)
     774             :         {
     775             :             /* While there is still excess width... */
     776         176 :             while (width_total > output_columns)
     777             :             {
     778         128 :                 double      max_ratio = 0;
     779         128 :                 int         worst_col = -1;
     780             : 
     781             :                 /*
     782             :                  * Find column that has the highest ratio of its maximum width
     783             :                  * compared to its average width.  This tells us which column
     784             :                  * will produce the fewest wrapped values if shortened.
     785             :                  * width_wrap starts as equal to max_width.
     786             :                  */
     787         384 :                 for (i = 0; i < col_count; i++)
     788             :                 {
     789         256 :                     if (width_average[i] && width_wrap[i] > width_header[i])
     790             :                     {
     791             :                         /* Penalize wide columns by 1% of their width */
     792             :                         double      ratio;
     793             : 
     794         512 :                         ratio = (double) width_wrap[i] / width_average[i] +
     795         256 :                             max_width[i] * 0.01;
     796         256 :                         if (ratio > max_ratio)
     797             :                         {
     798         168 :                             max_ratio = ratio;
     799         168 :                             worst_col = i;
     800             :                         }
     801             :                     }
     802             :                 }
     803             : 
     804             :                 /* Exit loop if we can't squeeze any more. */
     805         128 :                 if (worst_col == -1)
     806           0 :                     break;
     807             : 
     808             :                 /* Decrease width of target column by one. */
     809         128 :                 width_wrap[worst_col]--;
     810         128 :                 width_total--;
     811             :             }
     812             :         }
     813             :     }
     814             : 
     815             :     /*
     816             :      * If in expanded auto mode, we have now calculated the expected width, so
     817             :      * we can now escape to vertical mode if necessary.  If the output has
     818             :      * only one column, the expanded format would be wider than the regular
     819             :      * format, so don't use it in that case.
     820             :      */
     821       60868 :     if (cont->opt->expanded == 2 && output_columns > 0 && cont->ncolumns > 1 &&
     822           0 :         (output_columns < total_header_width || output_columns < width_total))
     823             :     {
     824           0 :         print_aligned_vertical(cont, fout, is_pager);
     825           0 :         goto cleanup;
     826             :     }
     827             : 
     828             :     /* If we wrapped beyond the display width, use the pager */
     829       60868 :     if (!is_pager && fout == stdout && output_columns > 0 &&
     830         288 :         (output_columns < total_header_width || output_columns < width_total))
     831             :     {
     832         148 :         fout = PageOutput(INT_MAX, cont->opt);   /* force pager */
     833         148 :         is_pager = is_local_pager = true;
     834             :     }
     835             : 
     836             :     /* Check if newlines or our wrapping now need the pager */
     837       60868 :     if (!is_pager && fout == stdout)
     838             :     {
     839             :         /* scan all cells, find maximum width, compute cell_count */
     840      672398 :         for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
     841             :         {
     842             :             int         width,
     843             :                         nl_lines,
     844             :                         bytes_required;
     845             : 
     846      611742 :             pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
     847             :                        &width, &nl_lines, &bytes_required);
     848             : 
     849             :             /*
     850             :              * A row can have both wrapping and newlines that cause it to
     851             :              * display across multiple lines.  We check for both cases below.
     852             :              */
     853      611742 :             if (width > 0 && width_wrap[i])
     854             :             {
     855             :                 unsigned int extra_lines;
     856             : 
     857             :                 /* don't count the first line of nl_lines - it's not "extra" */
     858      539924 :                 extra_lines = ((width - 1) / width_wrap[i]) + nl_lines - 1;
     859      539924 :                 if (extra_lines > extra_row_output_lines)
     860         922 :                     extra_row_output_lines = extra_lines;
     861             :             }
     862             : 
     863             :             /* i is the current column number: increment with wrap */
     864      611742 :             if (++i >= col_count)
     865             :             {
     866      252680 :                 i = 0;
     867             :                 /* At last column of each row, add tallest column height */
     868      252680 :                 extra_output_lines += extra_row_output_lines;
     869      252680 :                 extra_row_output_lines = 0;
     870             :             }
     871             :         }
     872       60656 :         IsPagerNeeded(cont, extra_output_lines, false, &fout, &is_pager);
     873       60656 :         is_local_pager = is_pager;
     874             :     }
     875             : 
     876             :     /* time to output */
     877       60868 :     if (cont->opt->start_table)
     878             :     {
     879             :         /* print title */
     880       60832 :         if (cont->title && !opt_tuples_only)
     881             :         {
     882             :             int         width,
     883             :                         height;
     884             : 
     885        2182 :             pg_wcssize((const unsigned char *) cont->title, strlen(cont->title),
     886             :                        encoding, &width, &height, NULL);
     887        2182 :             if (width >= width_total)
     888             :                 /* Aligned */
     889         118 :                 fprintf(fout, "%s\n", cont->title);
     890             :             else
     891             :                 /* Centered */
     892        2064 :                 fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "",
     893             :                         cont->title);
     894             :         }
     895             : 
     896             :         /* print headers */
     897       60832 :         if (!opt_tuples_only)
     898             :         {
     899             :             int         more_col_wrapping;
     900             :             int         curr_nl_line;
     901             : 
     902       60816 :             if (opt_border == 2)
     903          32 :                 _print_horizontal_line(col_count, width_wrap, opt_border,
     904             :                                        PRINT_RULE_TOP, format, fout);
     905             : 
     906      175704 :             for (i = 0; i < col_count; i++)
     907      344664 :                 pg_wcsformat((const unsigned char *) cont->headers[i],
     908      114888 :                              strlen(cont->headers[i]), encoding,
     909      114888 :                              col_lineptrs[i], max_nl_lines[i]);
     910             : 
     911       60816 :             more_col_wrapping = col_count;
     912       60816 :             curr_nl_line = 0;
     913       60816 :             memset(header_done, false, col_count * sizeof(bool));
     914      121660 :             while (more_col_wrapping)
     915             :             {
     916       60844 :                 if (opt_border == 2)
     917          64 :                     fputs(dformat->leftvrule, fout);
     918             : 
     919      175924 :                 for (i = 0; i < cont->ncolumns; i++)
     920             :                 {
     921      115080 :                     struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;
     922             :                     unsigned int nbspace;
     923             : 
     924      115080 :                     if (opt_border != 0 ||
     925         128 :                         (!format->wrap_right_border && i > 0))
     926      114984 :                         fputs(curr_nl_line ? format->header_nl_left : " ",
     927             :                               fout);
     928             : 
     929      115080 :                     if (!header_done[i])
     930             :                     {
     931      115032 :                         nbspace = width_wrap[i] - this_line->width;
     932             : 
     933             :                         /* centered */
     934      115032 :                         fprintf(fout, "%-*s%s%-*s",
     935      115032 :                                 nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
     936             : 
     937      115032 :                         if (!(this_line + 1)->ptr)
     938             :                         {
     939      114888 :                             more_col_wrapping--;
     940      114888 :                             header_done[i] = 1;
     941             :                         }
     942             :                     }
     943             :                     else
     944          48 :                         fprintf(fout, "%*s", width_wrap[i], "");
     945             : 
     946      115080 :                     if (opt_border != 0 || format->wrap_right_border)
     947      115016 :                         fputs(!header_done[i] ? format->header_nl_right : " ",
     948             :                               fout);
     949             : 
     950      115080 :                     if (opt_border != 0 && col_count > 0 && i < col_count - 1)
     951       54172 :                         fputs(dformat->midvrule, fout);
     952             :                 }
     953       60844 :                 curr_nl_line++;
     954             : 
     955       60844 :                 if (opt_border == 2)
     956          64 :                     fputs(dformat->rightvrule, fout);
     957       60844 :                 fputc('\n', fout);
     958             :             }
     959             : 
     960       60816 :             _print_horizontal_line(col_count, width_wrap, opt_border,
     961             :                                    PRINT_RULE_MIDDLE, format, fout);
     962             :         }
     963             :     }
     964             : 
     965             :     /* print cells, one loop per row */
     966      314296 :     for (i = 0, ptr = cont->cells; *ptr; i += col_count, ptr += col_count)
     967             :     {
     968             :         bool        more_lines;
     969             : 
     970      253428 :         if (cancel_pressed)
     971           0 :             break;
     972             : 
     973             :         /*
     974             :          * Format each cell.
     975             :          */
     976      867878 :         for (j = 0; j < col_count; j++)
     977             :         {
     978     1228900 :             pg_wcsformat((const unsigned char *) ptr[j], strlen(ptr[j]), encoding,
     979      614450 :                          col_lineptrs[j], max_nl_lines[j]);
     980      614450 :             curr_nl_line[j] = 0;
     981             :         }
     982             : 
     983      253428 :         memset(bytes_output, 0, col_count * sizeof(int));
     984             : 
     985             :         /*
     986             :          * Each time through this loop, one display line is output. It can
     987             :          * either be a full value or a partial value if embedded newlines
     988             :          * exist or if 'format=wrapping' mode is enabled.
     989             :          */
     990             :         do
     991             :         {
     992      262984 :             more_lines = false;
     993             : 
     994             :             /* left border */
     995      262984 :             if (opt_border == 2)
     996         368 :                 fputs(dformat->leftvrule, fout);
     997             : 
     998             :             /* for each column */
     999      889302 :             for (j = 0; j < col_count; j++)
    1000             :             {
    1001             :                 /* We have a valid array element, so index it */
    1002      626318 :                 struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]];
    1003             :                 int         bytes_to_output;
    1004      626318 :                 int         chars_to_output = width_wrap[j];
    1005     1251900 :                 bool        finalspaces = (opt_border == 2 ||
    1006      625582 :                                            (col_count > 0 && j < col_count - 1));
    1007             : 
    1008             :                 /* Print left-hand wrap or newline mark */
    1009      626318 :                 if (opt_border != 0)
    1010             :                 {
    1011      625678 :                     if (wrap[j] == PRINT_LINE_WRAP_WRAP)
    1012          80 :                         fputs(format->wrap_left, fout);
    1013      625598 :                     else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE)
    1014        9608 :                         fputs(format->nl_left, fout);
    1015             :                     else
    1016      615990 :                         fputc(' ', fout);
    1017             :                 }
    1018             : 
    1019      626318 :                 if (!this_line->ptr)
    1020             :                 {
    1021             :                     /* Past newline lines so just pad for other columns */
    1022        1924 :                     if (finalspaces)
    1023        1620 :                         fprintf(fout, "%*s", chars_to_output, "");
    1024             :                 }
    1025             :                 else
    1026             :                 {
    1027             :                     /* Get strlen() of the characters up to width_wrap */
    1028             :                     bytes_to_output =
    1029      624394 :                         strlen_max_width(this_line->ptr + bytes_output[j],
    1030             :                                          &chars_to_output, encoding);
    1031             : 
    1032             :                     /*
    1033             :                      * If we exceeded width_wrap, it means the display width
    1034             :                      * of a single character was wider than our target width.
    1035             :                      * In that case, we have to pretend we are only printing
    1036             :                      * the target display width and make the best of it.
    1037             :                      */
    1038      624394 :                     if (chars_to_output > width_wrap[j])
    1039           0 :                         chars_to_output = width_wrap[j];
    1040             : 
    1041      624394 :                     if (cont->aligns[j] == 'r') /* Right aligned cell */
    1042             :                     {
    1043             :                         /* spaces first */
    1044      246934 :                         fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
    1045      493868 :                         fputnbytes(fout,
    1046      246934 :                                    (char *) (this_line->ptr + bytes_output[j]),
    1047             :                                    bytes_to_output);
    1048             :                     }
    1049             :                     else        /* Left aligned cell */
    1050             :                     {
    1051             :                         /* spaces second */
    1052      754920 :                         fputnbytes(fout,
    1053      377460 :                                    (char *) (this_line->ptr + bytes_output[j]),
    1054             :                                    bytes_to_output);
    1055             :                     }
    1056             : 
    1057      624394 :                     bytes_output[j] += bytes_to_output;
    1058             : 
    1059             :                     /* Do we have more text to wrap? */
    1060      624394 :                     if (*(this_line->ptr + bytes_output[j]) != '\0')
    1061          80 :                         more_lines = true;
    1062             :                     else
    1063             :                     {
    1064             :                         /* Advance to next newline line */
    1065      624314 :                         curr_nl_line[j]++;
    1066      624314 :                         if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
    1067        9864 :                             more_lines = true;
    1068      624314 :                         bytes_output[j] = 0;
    1069             :                     }
    1070             :                 }
    1071             : 
    1072             :                 /* Determine next line's wrap status for this column */
    1073      626318 :                 wrap[j] = PRINT_LINE_WRAP_NONE;
    1074      626318 :                 if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
    1075             :                 {
    1076        9944 :                     if (bytes_output[j] != 0)
    1077          80 :                         wrap[j] = PRINT_LINE_WRAP_WRAP;
    1078        9864 :                     else if (curr_nl_line[j] != 0)
    1079        9864 :                         wrap[j] = PRINT_LINE_WRAP_NEWLINE;
    1080             :                 }
    1081             : 
    1082             :                 /*
    1083             :                  * If left-aligned, pad out remaining space if needed (not
    1084             :                  * last column, and/or wrap marks required).
    1085             :                  */
    1086      626318 :                 if (cont->aligns[j] != 'r') /* Left aligned cell */
    1087             :                 {
    1088      379216 :                     if (finalspaces ||
    1089      180026 :                         wrap[j] == PRINT_LINE_WRAP_WRAP ||
    1090      180018 :                         wrap[j] == PRINT_LINE_WRAP_NEWLINE)
    1091      208266 :                         fprintf(fout, "%*s",
    1092      208266 :                                 width_wrap[j] - chars_to_output, "");
    1093             :                 }
    1094             : 
    1095             :                 /* Print right-hand wrap or newline mark */
    1096      626318 :                 if (wrap[j] == PRINT_LINE_WRAP_WRAP)
    1097          80 :                     fputs(format->wrap_right, fout);
    1098      626238 :                 else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE)
    1099        9864 :                     fputs(format->nl_right, fout);
    1100      616374 :                 else if (opt_border == 2 || (col_count > 0 && j < col_count - 1))
    1101      362834 :                     fputc(' ', fout);
    1102             : 
    1103             :                 /* Print column divider, if not the last column */
    1104      626318 :                 if (opt_border != 0 && (col_count > 0 && j < col_count - 1))
    1105             :                 {
    1106      363014 :                     if (wrap[j + 1] == PRINT_LINE_WRAP_WRAP)
    1107          24 :                         fputs(format->midvrule_wrap, fout);
    1108      362990 :                     else if (wrap[j + 1] == PRINT_LINE_WRAP_NEWLINE)
    1109         648 :                         fputs(format->midvrule_nl, fout);
    1110      362342 :                     else if (col_lineptrs[j + 1][curr_nl_line[j + 1]].ptr == NULL)
    1111        1512 :                         fputs(format->midvrule_blank, fout);
    1112             :                     else
    1113      360830 :                         fputs(dformat->midvrule, fout);
    1114             :                 }
    1115             :             }
    1116             : 
    1117             :             /* end-of-row border */
    1118      262984 :             if (opt_border == 2)
    1119         368 :                 fputs(dformat->rightvrule, fout);
    1120      262984 :             fputc('\n', fout);
    1121             : 
    1122      262984 :         } while (more_lines);
    1123             :     }
    1124             : 
    1125       60868 :     if (cont->opt->stop_table)
    1126             :     {
    1127       60828 :         printTableFooter *footers = footers_with_default(cont);
    1128             : 
    1129       60828 :         if (opt_border == 2 && !cancel_pressed)
    1130          32 :             _print_horizontal_line(col_count, width_wrap, opt_border,
    1131             :                                    PRINT_RULE_BOTTOM, format, fout);
    1132             : 
    1133             :         /* print footers */
    1134       60828 :         if (footers && !opt_tuples_only && !cancel_pressed)
    1135             :         {
    1136             :             printTableFooter *f;
    1137             : 
    1138      124226 :             for (f = footers; f; f = f->next)
    1139       63626 :                 fprintf(fout, "%s\n", f->data);
    1140             :         }
    1141             : 
    1142       60828 :         fputc('\n', fout);
    1143             :     }
    1144             : 
    1145          40 : cleanup:
    1146             :     /* clean up */
    1147      175864 :     for (i = 0; i < col_count; i++)
    1148             :     {
    1149      114996 :         free(col_lineptrs[i]);
    1150      114996 :         free(format_buf[i]);
    1151             :     }
    1152       60868 :     free(width_header);
    1153       60868 :     free(width_average);
    1154       60868 :     free(max_width);
    1155       60868 :     free(width_wrap);
    1156       60868 :     free(max_nl_lines);
    1157       60868 :     free(curr_nl_line);
    1158       60868 :     free(col_lineptrs);
    1159       60868 :     free(max_bytes);
    1160       60868 :     free(format_buf);
    1161       60868 :     free(header_done);
    1162       60868 :     free(bytes_output);
    1163       60868 :     free(wrap);
    1164             : 
    1165       60868 :     if (is_local_pager)
    1166         148 :         ClosePager(fout);
    1167             : }
    1168             : 
    1169             : 
    1170             : static void
    1171        1082 : print_aligned_vertical_line(const printTextFormat *format,
    1172             :                             const unsigned short opt_border,
    1173             :                             unsigned long record,
    1174             :                             unsigned int hwidth,
    1175             :                             unsigned int dwidth,
    1176             :                             printTextRule pos,
    1177             :                             FILE *fout)
    1178             : {
    1179        1082 :     const printTextLineFormat *lformat = &format->lrule[pos];
    1180             :     unsigned int i;
    1181        1082 :     int         reclen = 0;
    1182             : 
    1183        1082 :     if (opt_border == 2)
    1184         312 :         fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
    1185         770 :     else if (opt_border == 1)
    1186         498 :         fputs(lformat->hrule, fout);
    1187             : 
    1188        1082 :     if (record)
    1189             :     {
    1190        1034 :         if (opt_border == 0)
    1191         272 :             reclen = fprintf(fout, "* Record %lu", record);
    1192             :         else
    1193         762 :             reclen = fprintf(fout, "[ RECORD %lu ]", record);
    1194             :     }
    1195        1082 :     if (opt_border != 2)
    1196         770 :         reclen++;
    1197        1082 :     if (reclen < 0)
    1198           0 :         reclen = 0;
    1199        5096 :     for (i = reclen; i < hwidth; i++)
    1200        4014 :         fputs(opt_border > 0 ? lformat->hrule : " ", fout);
    1201        1082 :     reclen -= hwidth;
    1202             : 
    1203        1082 :     if (opt_border > 0)
    1204             :     {
    1205         810 :         if (reclen-- <= 0)
    1206         702 :             fputs(lformat->hrule, fout);
    1207         810 :         if (reclen-- <= 0)
    1208         702 :             fputs(lformat->midvrule, fout);
    1209         810 :         if (reclen-- <= 0)
    1210         702 :             fputs(lformat->hrule, fout);
    1211             :     }
    1212             :     else
    1213             :     {
    1214         272 :         if (reclen-- <= 0)
    1215         240 :             fputc(' ', fout);
    1216             :     }
    1217        1082 :     if (reclen < 0)
    1218         942 :         reclen = 0;
    1219       18588 :     for (i = reclen; i < dwidth; i++)
    1220       17506 :         fputs(opt_border > 0 ? lformat->hrule : " ", fout);
    1221        1082 :     if (opt_border == 2)
    1222         312 :         fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
    1223        1082 :     fputc('\n', fout);
    1224        1082 : }
    1225             : 
    1226             : static void
    1227         366 : print_aligned_vertical(const printTableContent *cont,
    1228             :                        FILE *fout, bool is_pager)
    1229             : {
    1230         366 :     bool        opt_tuples_only = cont->opt->tuples_only;
    1231         366 :     unsigned short opt_border = cont->opt->border;
    1232         366 :     const printTextFormat *format = get_line_style(cont->opt);
    1233         366 :     const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
    1234         366 :     int         encoding = cont->opt->encoding;
    1235         366 :     unsigned long record = cont->opt->prior_records + 1;
    1236             :     const char *const *ptr;
    1237             :     unsigned int i,
    1238         366 :                 hwidth = 0,
    1239         366 :                 dwidth = 0,
    1240         366 :                 hheight = 1,
    1241         366 :                 dheight = 1,
    1242         366 :                 hformatsize = 0,
    1243         366 :                 dformatsize = 0;
    1244             :     struct lineptr *hlineptr,
    1245             :                *dlineptr;
    1246         366 :     bool        is_local_pager = false,
    1247         366 :                 hmultiline = false,
    1248         366 :                 dmultiline = false;
    1249         366 :     int         output_columns = 0; /* Width of interactive console */
    1250             : 
    1251         366 :     if (cancel_pressed)
    1252           0 :         return;
    1253             : 
    1254         366 :     if (opt_border > 2)
    1255           0 :         opt_border = 2;
    1256             : 
    1257         366 :     if (cont->cells[0] == NULL && cont->opt->start_table &&
    1258          12 :         cont->opt->stop_table)
    1259             :     {
    1260          12 :         printTableFooter *footers = footers_with_default(cont);
    1261             : 
    1262          12 :         if (!opt_tuples_only && !cancel_pressed && footers)
    1263             :         {
    1264             :             printTableFooter *f;
    1265             : 
    1266          24 :             for (f = footers; f; f = f->next)
    1267          12 :                 fprintf(fout, "%s\n", f->data);
    1268             :         }
    1269             : 
    1270          12 :         fputc('\n', fout);
    1271             : 
    1272          12 :         return;
    1273             :     }
    1274             : 
    1275             :     /*
    1276             :      * Deal with the pager here instead of in printTable(), because we could
    1277             :      * get here via print_aligned_text() in expanded auto mode, and so we have
    1278             :      * to recalculate the pager requirement based on vertical output.
    1279             :      */
    1280         354 :     if (!is_pager)
    1281             :     {
    1282         338 :         IsPagerNeeded(cont, 0, true, &fout, &is_pager);
    1283         338 :         is_local_pager = is_pager;
    1284             :     }
    1285             : 
    1286             :     /* Find the maximum dimensions for the headers */
    1287        2154 :     for (i = 0; i < cont->ncolumns; i++)
    1288             :     {
    1289             :         int         width,
    1290             :                     height,
    1291             :                     fs;
    1292             : 
    1293        1800 :         pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
    1294             :                    encoding, &width, &height, &fs);
    1295        1800 :         if (width > hwidth)
    1296         426 :             hwidth = width;
    1297        1800 :         if (height > hheight)
    1298             :         {
    1299          48 :             hheight = height;
    1300          48 :             hmultiline = true;
    1301             :         }
    1302        1800 :         if (fs > hformatsize)
    1303         426 :             hformatsize = fs;
    1304             :     }
    1305             : 
    1306             :     /* find longest data cell */
    1307        3570 :     for (i = 0, ptr = cont->cells; *ptr; ptr++, i++)
    1308             :     {
    1309             :         int         width,
    1310             :                     height,
    1311             :                     fs;
    1312             : 
    1313        3216 :         pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
    1314             :                    &width, &height, &fs);
    1315        3216 :         if (width > dwidth)
    1316         642 :             dwidth = width;
    1317        3216 :         if (height > dheight)
    1318             :         {
    1319          52 :             dheight = height;
    1320          52 :             dmultiline = true;
    1321             :         }
    1322        3216 :         if (fs > dformatsize)
    1323         642 :             dformatsize = fs;
    1324             :     }
    1325             : 
    1326             :     /*
    1327             :      * We now have all the information we need to setup the formatting
    1328             :      * structures
    1329             :      */
    1330         354 :     dlineptr = pg_malloc((sizeof(*dlineptr)) * (dheight + 1));
    1331         354 :     hlineptr = pg_malloc((sizeof(*hlineptr)) * (hheight + 1));
    1332             : 
    1333         354 :     dlineptr->ptr = pg_malloc(dformatsize);
    1334         354 :     hlineptr->ptr = pg_malloc(hformatsize);
    1335             : 
    1336         354 :     if (cont->opt->start_table)
    1337             :     {
    1338             :         /* print title */
    1339         346 :         if (!opt_tuples_only && cont->title)
    1340          12 :             fprintf(fout, "%s\n", cont->title);
    1341             :     }
    1342             : 
    1343             :     /*
    1344             :      * Choose target output width: \pset columns, or $COLUMNS, or ioctl
    1345             :      */
    1346         354 :     if (cont->opt->columns > 0)
    1347         136 :         output_columns = cont->opt->columns;
    1348         218 :     else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
    1349             :     {
    1350          16 :         if (cont->opt->env_columns > 0)
    1351           0 :             output_columns = cont->opt->env_columns;
    1352             : #ifdef TIOCGWINSZ
    1353             :         else
    1354             :         {
    1355             :             struct winsize screen_size;
    1356             : 
    1357          16 :             if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
    1358           0 :                 output_columns = screen_size.ws_col;
    1359             :         }
    1360             : #endif
    1361             :     }
    1362             : 
    1363             :     /*
    1364             :      * Calculate available width for data in wrapped mode
    1365             :      */
    1366         354 :     if (cont->opt->format == PRINT_WRAPPED)
    1367             :     {
    1368             :         unsigned int swidth,
    1369          68 :                     rwidth = 0,
    1370             :                     newdwidth;
    1371             : 
    1372          68 :         if (opt_border == 0)
    1373             :         {
    1374             :             /*
    1375             :              * For border = 0, one space in the middle.  (If we discover we
    1376             :              * need to wrap, the spacer column will be replaced by a wrap
    1377             :              * marker, and we'll make room below for another wrap marker at
    1378             :              * the end of the line.  But for now, assume no wrap is needed.)
    1379             :              */
    1380          20 :             swidth = 1;
    1381             : 
    1382             :             /* We might need a column for header newline markers, too */
    1383          20 :             if (hmultiline)
    1384           8 :                 swidth++;
    1385             :         }
    1386          48 :         else if (opt_border == 1)
    1387             :         {
    1388             :             /*
    1389             :              * For border = 1, two spaces and a vrule in the middle.  (As
    1390             :              * above, we might need one more column for a wrap marker.)
    1391             :              */
    1392          28 :             swidth = 3;
    1393             : 
    1394             :             /* We might need a column for left header newline markers, too */
    1395          28 :             if (hmultiline && (format == &pg_asciiformat_old))
    1396           4 :                 swidth++;
    1397             :         }
    1398             :         else
    1399             :         {
    1400             :             /*
    1401             :              * For border = 2, two more for the vrules at the beginning and
    1402             :              * end of the lines, plus spacer columns adjacent to these.  (We
    1403             :              * won't need extra columns for wrap/newline markers, we'll just
    1404             :              * repurpose the spacers.)
    1405             :              */
    1406          20 :             swidth = 7;
    1407             :         }
    1408             : 
    1409             :         /* Reserve a column for data newline indicators, too, if needed */
    1410          68 :         if (dmultiline &&
    1411          16 :             opt_border < 2 && format != &pg_asciiformat_old)
    1412           8 :             swidth++;
    1413             : 
    1414             :         /* Determine width required for record header lines */
    1415          68 :         if (!opt_tuples_only)
    1416             :         {
    1417          64 :             if (cont->nrows > 0)
    1418          64 :                 rwidth = 1 + (int) log10(cont->nrows);
    1419          64 :             if (opt_border == 0)
    1420          20 :                 rwidth += 9;    /* "* RECORD " */
    1421          44 :             else if (opt_border == 1)
    1422          24 :                 rwidth += 12;   /* "-[ RECORD  ]" */
    1423             :             else
    1424          20 :                 rwidth += 15;   /* "+-[ RECORD  ]-+" */
    1425             :         }
    1426             : 
    1427             :         /* We might need to do the rest of the calculation twice */
    1428             :         for (;;)
    1429          16 :         {
    1430             :             unsigned int width;
    1431             : 
    1432             :             /* Total width required to not wrap data */
    1433          84 :             width = hwidth + swidth + dwidth;
    1434             :             /* ... and not the header lines, either */
    1435          84 :             if (width < rwidth)
    1436           0 :                 width = rwidth;
    1437             : 
    1438          84 :             if (output_columns > 0)
    1439             :             {
    1440             :                 unsigned int min_width;
    1441             : 
    1442             :                 /* Minimum acceptable width: room for just 3 columns of data */
    1443          84 :                 min_width = hwidth + swidth + 3;
    1444             :                 /* ... but not less than what the record header lines need */
    1445          84 :                 if (min_width < rwidth)
    1446          24 :                     min_width = rwidth;
    1447             : 
    1448          84 :                 if (output_columns >= width)
    1449             :                 {
    1450             :                     /* Plenty of room, use native data width */
    1451             :                     /* (but at least enough for the record header lines) */
    1452          16 :                     newdwidth = width - hwidth - swidth;
    1453             :                 }
    1454          68 :                 else if (output_columns < min_width)
    1455             :                 {
    1456             :                     /* Set data width to match min_width */
    1457          16 :                     newdwidth = min_width - hwidth - swidth;
    1458             :                 }
    1459             :                 else
    1460             :                 {
    1461             :                     /* Set data width to match output_columns */
    1462          52 :                     newdwidth = output_columns - hwidth - swidth;
    1463             :                 }
    1464             :             }
    1465             :             else
    1466             :             {
    1467             :                 /* Don't know the wrap limit, so use native data width */
    1468             :                 /* (but at least enough for the record header lines) */
    1469           0 :                 newdwidth = width - hwidth - swidth;
    1470             :             }
    1471             : 
    1472             :             /*
    1473             :              * If we will need to wrap data and didn't already allocate a data
    1474             :              * newline/wrap marker column, do so and recompute.
    1475             :              */
    1476          84 :             if (newdwidth < dwidth && !dmultiline &&
    1477          16 :                 opt_border < 2 && format != &pg_asciiformat_old)
    1478             :             {
    1479          16 :                 dmultiline = true;
    1480          16 :                 swidth++;
    1481             :             }
    1482             :             else
    1483             :                 break;
    1484             :         }
    1485             : 
    1486          68 :         dwidth = newdwidth;
    1487             :     }
    1488             : 
    1489             :     /* print records */
    1490        3570 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1491             :     {
    1492             :         printTextRule pos;
    1493             :         int         dline,
    1494             :                     hline,
    1495             :                     dcomplete,
    1496             :                     hcomplete,
    1497             :                     offset,
    1498             :                     chars_to_output;
    1499             : 
    1500        3216 :         if (cancel_pressed)
    1501           0 :             break;
    1502             : 
    1503        3216 :         if (i == 0)
    1504         346 :             pos = PRINT_RULE_TOP;
    1505             :         else
    1506        2870 :             pos = PRINT_RULE_MIDDLE;
    1507             : 
    1508             :         /* Print record header (e.g. "[ RECORD N ]") above each record */
    1509        3216 :         if (i % cont->ncolumns == 0)
    1510             :         {
    1511        1050 :             unsigned int lhwidth = hwidth;
    1512             : 
    1513        1050 :             if ((opt_border < 2) &&
    1514          64 :                 (hmultiline) &&
    1515             :                 (format == &pg_asciiformat_old))
    1516          32 :                 lhwidth++;      /* for newline indicators */
    1517             : 
    1518        1050 :             if (!opt_tuples_only)
    1519        1034 :                 print_aligned_vertical_line(format, opt_border, record++,
    1520             :                                             lhwidth, dwidth, pos, fout);
    1521          16 :             else if (i != 0 || !cont->opt->start_table || opt_border == 2)
    1522           8 :                 print_aligned_vertical_line(format, opt_border, 0, lhwidth,
    1523             :                                             dwidth, pos, fout);
    1524             :         }
    1525             : 
    1526             :         /* Format the header */
    1527        6432 :         pg_wcsformat((const unsigned char *) cont->headers[i % cont->ncolumns],
    1528        3216 :                      strlen(cont->headers[i % cont->ncolumns]),
    1529             :                      encoding, hlineptr, hheight);
    1530             :         /* Format the data */
    1531        3216 :         pg_wcsformat((const unsigned char *) *ptr, strlen(*ptr), encoding,
    1532             :                      dlineptr, dheight);
    1533             : 
    1534             :         /*
    1535             :          * Loop through header and data in parallel dealing with newlines and
    1536             :          * wrapped lines until they're both exhausted
    1537             :          */
    1538        3216 :         dline = hline = 0;
    1539        3216 :         dcomplete = hcomplete = 0;
    1540        3216 :         offset = 0;
    1541        3216 :         chars_to_output = dlineptr[dline].width;
    1542        8264 :         while (!dcomplete || !hcomplete)
    1543             :         {
    1544             :             /* Left border */
    1545        5048 :             if (opt_border == 2)
    1546        1212 :                 fprintf(fout, "%s", dformat->leftvrule);
    1547             : 
    1548             :             /* Header (never wrapped so just need to deal with newlines) */
    1549        5048 :             if (!hcomplete)
    1550             :             {
    1551        3504 :                 int         swidth = hwidth,
    1552        3504 :                             target_width = hwidth;
    1553             : 
    1554             :                 /*
    1555             :                  * Left spacer or new line indicator
    1556             :                  */
    1557        3504 :                 if ((opt_border == 2) ||
    1558         320 :                     (hmultiline && (format == &pg_asciiformat_old)))
    1559         800 :                     fputs(hline ? format->header_nl_left : " ", fout);
    1560             : 
    1561             :                 /*
    1562             :                  * Header text
    1563             :                  */
    1564        3504 :                 strlen_max_width(hlineptr[hline].ptr, &target_width,
    1565             :                                  encoding);
    1566        3504 :                 fprintf(fout, "%-s", hlineptr[hline].ptr);
    1567             : 
    1568             :                 /*
    1569             :                  * Spacer
    1570             :                  */
    1571        3504 :                 swidth -= target_width;
    1572        3504 :                 if (swidth > 0)
    1573        2182 :                     fprintf(fout, "%*s", swidth, " ");
    1574             : 
    1575             :                 /*
    1576             :                  * New line indicator or separator's space
    1577             :                  */
    1578        3504 :                 if (hlineptr[hline + 1].ptr)
    1579             :                 {
    1580             :                     /* More lines after this one due to a newline */
    1581         288 :                     if ((opt_border > 0) ||
    1582          96 :                         (hmultiline && (format != &pg_asciiformat_old)))
    1583         240 :                         fputs(format->header_nl_right, fout);
    1584         288 :                     hline++;
    1585             :                 }
    1586             :                 else
    1587             :                 {
    1588             :                     /* This was the last line of the header */
    1589        3216 :                     if ((opt_border > 0) ||
    1590          64 :                         (hmultiline && (format != &pg_asciiformat_old)))
    1591        2704 :                         fputs(" ", fout);
    1592        3216 :                     hcomplete = 1;
    1593             :                 }
    1594             :             }
    1595             :             else
    1596             :             {
    1597        1544 :                 unsigned int swidth = hwidth + opt_border;
    1598             : 
    1599        1544 :                 if ((opt_border < 2) &&
    1600         472 :                     (hmultiline) &&
    1601             :                     (format == &pg_asciiformat_old))
    1602         232 :                     swidth++;
    1603             : 
    1604        1544 :                 if ((opt_border == 0) &&
    1605         364 :                     (format != &pg_asciiformat_old) &&
    1606             :                     (hmultiline))
    1607         120 :                     swidth++;
    1608             : 
    1609        1544 :                 fprintf(fout, "%*s", swidth, " ");
    1610             :             }
    1611             : 
    1612             :             /* Separator */
    1613        5048 :             if (opt_border > 0)
    1614             :             {
    1615        3932 :                 if (offset)
    1616         744 :                     fputs(format->midvrule_wrap, fout);
    1617        3188 :                 else if (dline == 0)
    1618        2672 :                     fputs(dformat->midvrule, fout);
    1619             :                 else
    1620         516 :                     fputs(format->midvrule_nl, fout);
    1621             :             }
    1622             : 
    1623             :             /* Data */
    1624        5048 :             if (!dcomplete)
    1625             :             {
    1626        4928 :                 int         target_width = dwidth,
    1627             :                             bytes_to_output,
    1628        4928 :                             swidth = dwidth;
    1629             : 
    1630             :                 /*
    1631             :                  * Left spacer or wrap indicator
    1632             :                  */
    1633        4928 :                 fputs(offset == 0 ? " " : format->wrap_left, fout);
    1634             : 
    1635             :                 /*
    1636             :                  * Data text
    1637             :                  */
    1638        4928 :                 bytes_to_output = strlen_max_width(dlineptr[dline].ptr + offset,
    1639             :                                                    &target_width, encoding);
    1640        4928 :                 fputnbytes(fout, (char *) (dlineptr[dline].ptr + offset),
    1641             :                            bytes_to_output);
    1642             : 
    1643        4928 :                 chars_to_output -= target_width;
    1644        4928 :                 offset += bytes_to_output;
    1645             : 
    1646             :                 /* Spacer */
    1647        4928 :                 swidth -= target_width;
    1648             : 
    1649        4928 :                 if (chars_to_output)
    1650             :                 {
    1651             :                     /* continuing a wrapped column */
    1652         940 :                     if ((opt_border > 1) ||
    1653         568 :                         (dmultiline && (format != &pg_asciiformat_old)))
    1654             :                     {
    1655         908 :                         if (swidth > 0)
    1656           0 :                             fprintf(fout, "%*s", swidth, " ");
    1657         908 :                         fputs(format->wrap_right, fout);
    1658             :                     }
    1659             :                 }
    1660        3988 :                 else if (dlineptr[dline + 1].ptr)
    1661             :                 {
    1662             :                     /* reached a newline in the column */
    1663         772 :                     if ((opt_border > 1) ||
    1664         516 :                         (dmultiline && (format != &pg_asciiformat_old)))
    1665             :                     {
    1666         516 :                         if (swidth > 0)
    1667         504 :                             fprintf(fout, "%*s", swidth, " ");
    1668         516 :                         fputs(format->nl_right, fout);
    1669             :                     }
    1670         772 :                     dline++;
    1671         772 :                     offset = 0;
    1672         772 :                     chars_to_output = dlineptr[dline].width;
    1673             :                 }
    1674             :                 else
    1675             :                 {
    1676             :                     /* reached the end of the cell */
    1677        3216 :                     if (opt_border > 1)
    1678             :                     {
    1679         544 :                         if (swidth > 0)
    1680         492 :                             fprintf(fout, "%*s", swidth, " ");
    1681         544 :                         fputs(" ", fout);
    1682             :                     }
    1683        3216 :                     dcomplete = 1;
    1684             :                 }
    1685             : 
    1686             :                 /* Right border */
    1687        4928 :                 if (opt_border == 2)
    1688        1172 :                     fputs(dformat->rightvrule, fout);
    1689             : 
    1690        4928 :                 fputs("\n", fout);
    1691             :             }
    1692             :             else
    1693             :             {
    1694             :                 /*
    1695             :                  * data exhausted (this can occur if header is longer than the
    1696             :                  * data due to newlines in the header)
    1697             :                  */
    1698         120 :                 if (opt_border < 2)
    1699          80 :                     fputs("\n", fout);
    1700             :                 else
    1701          40 :                     fprintf(fout, "%*s  %s\n", dwidth, "", dformat->rightvrule);
    1702             :             }
    1703             :         }
    1704             :     }
    1705             : 
    1706         354 :     if (cont->opt->stop_table)
    1707             :     {
    1708         346 :         if (opt_border == 2 && !cancel_pressed)
    1709          40 :             print_aligned_vertical_line(format, opt_border, 0, hwidth, dwidth,
    1710             :                                         PRINT_RULE_BOTTOM, fout);
    1711             : 
    1712             :         /* print footers */
    1713         346 :         if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
    1714             :         {
    1715             :             printTableFooter *f;
    1716             : 
    1717           8 :             if (opt_border < 2)
    1718           8 :                 fputc('\n', fout);
    1719          16 :             for (f = cont->footers; f; f = f->next)
    1720           8 :                 fprintf(fout, "%s\n", f->data);
    1721             :         }
    1722             : 
    1723         346 :         fputc('\n', fout);
    1724             :     }
    1725             : 
    1726         354 :     free(hlineptr->ptr);
    1727         354 :     free(dlineptr->ptr);
    1728         354 :     free(hlineptr);
    1729         354 :     free(dlineptr);
    1730             : 
    1731         354 :     if (is_local_pager)
    1732           0 :         ClosePager(fout);
    1733             : }
    1734             : 
    1735             : 
    1736             : /**********************/
    1737             : /* CSV format         */
    1738             : /**********************/
    1739             : 
    1740             : 
    1741             : static void
    1742          72 : csv_escaped_print(const char *str, FILE *fout)
    1743             : {
    1744             :     const char *p;
    1745             : 
    1746          72 :     fputc('"', fout);
    1747         616 :     for (p = str; *p; p++)
    1748             :     {
    1749         544 :         if (*p == '"')
    1750          28 :             fputc('"', fout);  /* double quotes are doubled */
    1751         544 :         fputc(*p, fout);
    1752             :     }
    1753          72 :     fputc('"', fout);
    1754          72 : }
    1755             : 
    1756             : static void
    1757         416 : csv_print_field(const char *str, FILE *fout, char sep)
    1758             : {
    1759             :     /*----------------
    1760             :      * Enclose and escape field contents when one of these conditions is met:
    1761             :      * - the field separator is found in the contents.
    1762             :      * - the field contains a CR or LF.
    1763             :      * - the field contains a double quote.
    1764             :      * - the field is exactly "\.".
    1765             :      * - the field separator is either "\" or ".".
    1766             :      * The last two cases prevent producing a line that the server's COPY
    1767             :      * command would interpret as an end-of-data marker.  We only really
    1768             :      * need to ensure that the complete line isn't exactly "\.", but for
    1769             :      * simplicity we apply stronger restrictions here.
    1770             :      *----------------
    1771             :      */
    1772         416 :     if (strchr(str, sep) != NULL ||
    1773         408 :         strcspn(str, "\r\n\"") != strlen(str) ||
    1774         364 :         strcmp(str, "\\.") == 0 ||
    1775         360 :         sep == '\\' || sep == '.')
    1776          72 :         csv_escaped_print(str, fout);
    1777             :     else
    1778         344 :         fputs(str, fout);
    1779         416 : }
    1780             : 
    1781             : static void
    1782          32 : print_csv_text(const printTableContent *cont, FILE *fout)
    1783             : {
    1784             :     const char *const *ptr;
    1785             :     int         i;
    1786             : 
    1787          32 :     if (cancel_pressed)
    1788           0 :         return;
    1789             : 
    1790             :     /*
    1791             :      * The title and footer are never printed in csv format. The header is
    1792             :      * printed if opt_tuples_only is false.
    1793             :      *
    1794             :      * Despite RFC 4180 saying that end of lines are CRLF, terminate lines
    1795             :      * with '\n', which prints out as the system-dependent EOL string in text
    1796             :      * mode (typically LF on Unix and CRLF on Windows).
    1797             :      */
    1798          32 :     if (cont->opt->start_table && !cont->opt->tuples_only)
    1799             :     {
    1800             :         /* print headers */
    1801         108 :         for (ptr = cont->headers; *ptr; ptr++)
    1802             :         {
    1803          80 :             if (ptr != cont->headers)
    1804          52 :                 fputc(cont->opt->csvFieldSep[0], fout);
    1805          80 :             csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
    1806             :         }
    1807          28 :         fputc('\n', fout);
    1808             :     }
    1809             : 
    1810             :     /* print cells */
    1811         168 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1812             :     {
    1813         136 :         csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
    1814         136 :         if ((i + 1) % cont->ncolumns)
    1815          96 :             fputc(cont->opt->csvFieldSep[0], fout);
    1816             :         else
    1817          40 :             fputc('\n', fout);
    1818             :     }
    1819             : }
    1820             : 
    1821             : static void
    1822          12 : print_csv_vertical(const printTableContent *cont, FILE *fout)
    1823             : {
    1824             :     const char *const *ptr;
    1825             :     int         i;
    1826             : 
    1827             :     /* print records */
    1828         112 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1829             :     {
    1830         100 :         if (cancel_pressed)
    1831           0 :             return;
    1832             : 
    1833             :         /* print name of column */
    1834         100 :         csv_print_field(cont->headers[i % cont->ncolumns], fout,
    1835         100 :                         cont->opt->csvFieldSep[0]);
    1836             : 
    1837             :         /* print field separator */
    1838         100 :         fputc(cont->opt->csvFieldSep[0], fout);
    1839             : 
    1840             :         /* print field value */
    1841         100 :         csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
    1842             : 
    1843         100 :         fputc('\n', fout);
    1844             :     }
    1845             : }
    1846             : 
    1847             : 
    1848             : /**********************/
    1849             : /* HTML               */
    1850             : /**********************/
    1851             : 
    1852             : 
    1853             : void
    1854         548 : html_escaped_print(const char *in, FILE *fout)
    1855             : {
    1856             :     const char *p;
    1857         548 :     bool        leading_space = true;
    1858             : 
    1859        4600 :     for (p = in; *p; p++)
    1860             :     {
    1861        4052 :         switch (*p)
    1862             :         {
    1863          36 :             case '&':
    1864          36 :                 fputs("&amp;", fout);
    1865          36 :                 break;
    1866          96 :             case '<':
    1867          96 :                 fputs("&lt;", fout);
    1868          96 :                 break;
    1869          96 :             case '>':
    1870          96 :                 fputs("&gt;", fout);
    1871          96 :                 break;
    1872          48 :             case '\n':
    1873          48 :                 fputs("<br />\n", fout);
    1874          48 :                 break;
    1875          64 :             case '"':
    1876          64 :                 fputs("&quot;", fout);
    1877          64 :                 break;
    1878         180 :             case ' ':
    1879             :                 /* protect leading space, for EXPLAIN output */
    1880         180 :                 if (leading_space)
    1881          96 :                     fputs("&nbsp;", fout);
    1882             :                 else
    1883          84 :                     fputs(" ", fout);
    1884         180 :                 break;
    1885        3532 :             default:
    1886        3532 :                 fputc(*p, fout);
    1887             :         }
    1888        4052 :         if (*p != ' ')
    1889        3872 :             leading_space = false;
    1890             :     }
    1891         548 : }
    1892             : 
    1893             : 
    1894             : static void
    1895          20 : print_html_text(const printTableContent *cont, FILE *fout)
    1896             : {
    1897          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    1898          20 :     unsigned short opt_border = cont->opt->border;
    1899          20 :     const char *opt_table_attr = cont->opt->tableAttr;
    1900             :     unsigned int i;
    1901             :     const char *const *ptr;
    1902             : 
    1903          20 :     if (cancel_pressed)
    1904           0 :         return;
    1905             : 
    1906          20 :     if (cont->opt->start_table)
    1907             :     {
    1908          20 :         fprintf(fout, "<table border=\"%d\"", opt_border);
    1909          20 :         if (opt_table_attr)
    1910           4 :             fprintf(fout, " %s", opt_table_attr);
    1911          20 :         fputs(">\n", fout);
    1912             : 
    1913             :         /* print title */
    1914          20 :         if (!opt_tuples_only && cont->title)
    1915             :         {
    1916           4 :             fputs("  <caption>", fout);
    1917           4 :             html_escaped_print(cont->title, fout);
    1918           4 :             fputs("</caption>\n", fout);
    1919             :         }
    1920             : 
    1921             :         /* print headers */
    1922          20 :         if (!opt_tuples_only)
    1923             :         {
    1924          16 :             fputs("  <tr>\n", fout);
    1925          92 :             for (ptr = cont->headers; *ptr; ptr++)
    1926             :             {
    1927          76 :                 fputs("    <th align=\"center\">", fout);
    1928          76 :                 html_escaped_print(*ptr, fout);
    1929          76 :                 fputs("</th>\n", fout);
    1930             :             }
    1931          16 :             fputs("  </tr>\n", fout);
    1932             :         }
    1933             :     }
    1934             : 
    1935             :     /* print cells */
    1936         184 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    1937             :     {
    1938         164 :         if (i % cont->ncolumns == 0)
    1939             :         {
    1940          36 :             if (cancel_pressed)
    1941           0 :                 break;
    1942          36 :             fputs("  <tr valign=\"top\">\n", fout);
    1943             :         }
    1944             : 
    1945         164 :         fprintf(fout, "    <td align=\"%s\">", cont->aligns[(i) % cont->ncolumns] == 'r' ? "right" : "left");
    1946             :         /* is string only whitespace? */
    1947         164 :         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
    1948          24 :             fputs("&nbsp; ", fout);
    1949             :         else
    1950         140 :             html_escaped_print(*ptr, fout);
    1951             : 
    1952         164 :         fputs("</td>\n", fout);
    1953             : 
    1954         164 :         if ((i + 1) % cont->ncolumns == 0)
    1955          36 :             fputs("  </tr>\n", fout);
    1956             :     }
    1957             : 
    1958          20 :     if (cont->opt->stop_table)
    1959             :     {
    1960          20 :         printTableFooter *footers = footers_with_default(cont);
    1961             : 
    1962          20 :         fputs("</table>\n", fout);
    1963             : 
    1964             :         /* print footers */
    1965          20 :         if (!opt_tuples_only && footers != NULL && !cancel_pressed)
    1966             :         {
    1967             :             printTableFooter *f;
    1968             : 
    1969          16 :             fputs("<p>", fout);
    1970          32 :             for (f = footers; f; f = f->next)
    1971             :             {
    1972          16 :                 html_escaped_print(f->data, fout);
    1973          16 :                 fputs("<br />\n", fout);
    1974             :             }
    1975          16 :             fputs("</p>", fout);
    1976             :         }
    1977             : 
    1978          20 :         fputc('\n', fout);
    1979             :     }
    1980             : }
    1981             : 
    1982             : 
    1983             : static void
    1984          20 : print_html_vertical(const printTableContent *cont, FILE *fout)
    1985             : {
    1986          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    1987          20 :     unsigned short opt_border = cont->opt->border;
    1988          20 :     const char *opt_table_attr = cont->opt->tableAttr;
    1989          20 :     unsigned long record = cont->opt->prior_records + 1;
    1990             :     unsigned int i;
    1991             :     const char *const *ptr;
    1992             : 
    1993          20 :     if (cancel_pressed)
    1994           0 :         return;
    1995             : 
    1996          20 :     if (cont->opt->start_table)
    1997             :     {
    1998          20 :         fprintf(fout, "<table border=\"%d\"", opt_border);
    1999          20 :         if (opt_table_attr)
    2000           4 :             fprintf(fout, " %s", opt_table_attr);
    2001          20 :         fputs(">\n", fout);
    2002             : 
    2003             :         /* print title */
    2004          20 :         if (!opt_tuples_only && cont->title)
    2005             :         {
    2006           4 :             fputs("  <caption>", fout);
    2007           4 :             html_escaped_print(cont->title, fout);
    2008           4 :             fputs("</caption>\n", fout);
    2009             :         }
    2010             :     }
    2011             : 
    2012             :     /* print records */
    2013         184 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2014             :     {
    2015         164 :         if (i % cont->ncolumns == 0)
    2016             :         {
    2017          36 :             if (cancel_pressed)
    2018           0 :                 break;
    2019          36 :             if (!opt_tuples_only)
    2020          28 :                 fprintf(fout,
    2021             :                         "\n  <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n",
    2022             :                         record++);
    2023             :             else
    2024           8 :                 fputs("\n  <tr><td colspan=\"2\">&nbsp;</td></tr>\n", fout);
    2025             :         }
    2026         164 :         fputs("  <tr valign=\"top\">\n"
    2027             :               "    <th>", fout);
    2028         164 :         html_escaped_print(cont->headers[i % cont->ncolumns], fout);
    2029         164 :         fputs("</th>\n", fout);
    2030             : 
    2031         164 :         fprintf(fout, "    <td align=\"%s\">", cont->aligns[i % cont->ncolumns] == 'r' ? "right" : "left");
    2032             :         /* is string only whitespace? */
    2033         164 :         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
    2034          24 :             fputs("&nbsp; ", fout);
    2035             :         else
    2036         140 :             html_escaped_print(*ptr, fout);
    2037             : 
    2038         164 :         fputs("</td>\n  </tr>\n", fout);
    2039             :     }
    2040             : 
    2041          20 :     if (cont->opt->stop_table)
    2042             :     {
    2043          20 :         fputs("</table>\n", fout);
    2044             : 
    2045             :         /* print footers */
    2046          20 :         if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
    2047             :         {
    2048             :             printTableFooter *f;
    2049             : 
    2050           4 :             fputs("<p>", fout);
    2051           8 :             for (f = cont->footers; f; f = f->next)
    2052             :             {
    2053           4 :                 html_escaped_print(f->data, fout);
    2054           4 :                 fputs("<br />\n", fout);
    2055             :             }
    2056           4 :             fputs("</p>", fout);
    2057             :         }
    2058             : 
    2059          20 :         fputc('\n', fout);
    2060             :     }
    2061             : }
    2062             : 
    2063             : 
    2064             : /*************************/
    2065             : /* ASCIIDOC              */
    2066             : /*************************/
    2067             : 
    2068             : 
    2069             : static void
    2070         436 : asciidoc_escaped_print(const char *in, FILE *fout)
    2071             : {
    2072             :     const char *p;
    2073             : 
    2074        3060 :     for (p = in; *p; p++)
    2075             :     {
    2076        2624 :         switch (*p)
    2077             :         {
    2078          84 :             case '|':
    2079          84 :                 fputs("\\|", fout);
    2080          84 :                 break;
    2081        2540 :             default:
    2082        2540 :                 fputc(*p, fout);
    2083             :         }
    2084             :     }
    2085         436 : }
    2086             : 
    2087             : static void
    2088          20 : print_asciidoc_text(const printTableContent *cont, FILE *fout)
    2089             : {
    2090          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2091          20 :     unsigned short opt_border = cont->opt->border;
    2092             :     unsigned int i;
    2093             :     const char *const *ptr;
    2094             : 
    2095          20 :     if (cancel_pressed)
    2096           0 :         return;
    2097             : 
    2098          20 :     if (cont->opt->start_table)
    2099             :     {
    2100             :         /* print table in new paragraph - enforce preliminary new line */
    2101          20 :         fputs("\n", fout);
    2102             : 
    2103             :         /* print title */
    2104          20 :         if (!opt_tuples_only && cont->title)
    2105             :         {
    2106           4 :             fputs(".", fout);
    2107           4 :             fputs(cont->title, fout);
    2108           4 :             fputs("\n", fout);
    2109             :         }
    2110             : 
    2111             :         /* print table [] header definition */
    2112          20 :         fprintf(fout, "[%scols=\"", !opt_tuples_only ? "options=\"header\"," : "");
    2113         104 :         for (i = 0; i < cont->ncolumns; i++)
    2114             :         {
    2115          84 :             if (i != 0)
    2116          64 :                 fputs(",", fout);
    2117          84 :             fprintf(fout, "%s", cont->aligns[(i) % cont->ncolumns] == 'r' ? ">l" : "<l");
    2118             :         }
    2119          20 :         fputs("\"", fout);
    2120          20 :         switch (opt_border)
    2121             :         {
    2122           4 :             case 0:
    2123           4 :                 fputs(",frame=\"none\",grid=\"none\"", fout);
    2124           4 :                 break;
    2125          12 :             case 1:
    2126          12 :                 fputs(",frame=\"none\"", fout);
    2127          12 :                 break;
    2128           4 :             case 2:
    2129           4 :                 fputs(",frame=\"all\",grid=\"all\"", fout);
    2130           4 :                 break;
    2131             :         }
    2132          20 :         fputs("]\n", fout);
    2133          20 :         fputs("|====\n", fout);
    2134             : 
    2135             :         /* print headers */
    2136          20 :         if (!opt_tuples_only)
    2137             :         {
    2138          80 :             for (ptr = cont->headers; *ptr; ptr++)
    2139             :             {
    2140          64 :                 if (ptr != cont->headers)
    2141          48 :                     fputs(" ", fout);
    2142          64 :                 fputs("^l|", fout);
    2143          64 :                 asciidoc_escaped_print(*ptr, fout);
    2144             :             }
    2145          16 :             fputs("\n", fout);
    2146             :         }
    2147             :     }
    2148             : 
    2149             :     /* print cells */
    2150         160 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2151             :     {
    2152         140 :         if (i % cont->ncolumns == 0)
    2153             :         {
    2154          36 :             if (cancel_pressed)
    2155           0 :                 break;
    2156             :         }
    2157             : 
    2158         140 :         if (i % cont->ncolumns != 0)
    2159         104 :             fputs(" ", fout);
    2160         140 :         fputs("|", fout);
    2161             : 
    2162             :         /* protect against needless spaces */
    2163         140 :         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
    2164             :         {
    2165          24 :             if ((i + 1) % cont->ncolumns != 0)
    2166          24 :                 fputs(" ", fout);
    2167             :         }
    2168             :         else
    2169         116 :             asciidoc_escaped_print(*ptr, fout);
    2170             : 
    2171         140 :         if ((i + 1) % cont->ncolumns == 0)
    2172          36 :             fputs("\n", fout);
    2173             :     }
    2174             : 
    2175          20 :     fputs("|====\n", fout);
    2176             : 
    2177          20 :     if (cont->opt->stop_table)
    2178             :     {
    2179          20 :         printTableFooter *footers = footers_with_default(cont);
    2180             : 
    2181             :         /* print footers */
    2182          20 :         if (!opt_tuples_only && footers != NULL && !cancel_pressed)
    2183             :         {
    2184             :             printTableFooter *f;
    2185             : 
    2186          16 :             fputs("\n....\n", fout);
    2187          32 :             for (f = footers; f; f = f->next)
    2188             :             {
    2189          16 :                 fputs(f->data, fout);
    2190          16 :                 fputs("\n", fout);
    2191             :             }
    2192          16 :             fputs("....\n", fout);
    2193             :         }
    2194             :     }
    2195             : }
    2196             : 
    2197             : static void
    2198          20 : print_asciidoc_vertical(const printTableContent *cont, FILE *fout)
    2199             : {
    2200          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2201          20 :     unsigned short opt_border = cont->opt->border;
    2202          20 :     unsigned long record = cont->opt->prior_records + 1;
    2203             :     unsigned int i;
    2204             :     const char *const *ptr;
    2205             : 
    2206          20 :     if (cancel_pressed)
    2207           0 :         return;
    2208             : 
    2209          20 :     if (cont->opt->start_table)
    2210             :     {
    2211             :         /* print table in new paragraph - enforce preliminary new line */
    2212          20 :         fputs("\n", fout);
    2213             : 
    2214             :         /* print title */
    2215          20 :         if (!opt_tuples_only && cont->title)
    2216             :         {
    2217           4 :             fputs(".", fout);
    2218           4 :             fputs(cont->title, fout);
    2219           4 :             fputs("\n", fout);
    2220             :         }
    2221             : 
    2222             :         /* print table [] header definition */
    2223          20 :         fputs("[cols=\"h,l\"", fout);
    2224          20 :         switch (opt_border)
    2225             :         {
    2226           4 :             case 0:
    2227           4 :                 fputs(",frame=\"none\",grid=\"none\"", fout);
    2228           4 :                 break;
    2229          12 :             case 1:
    2230          12 :                 fputs(",frame=\"none\"", fout);
    2231          12 :                 break;
    2232           4 :             case 2:
    2233           4 :                 fputs(",frame=\"all\",grid=\"all\"", fout);
    2234           4 :                 break;
    2235             :         }
    2236          20 :         fputs("]\n", fout);
    2237          20 :         fputs("|====\n", fout);
    2238             :     }
    2239             : 
    2240             :     /* print records */
    2241         160 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2242             :     {
    2243         140 :         if (i % cont->ncolumns == 0)
    2244             :         {
    2245          36 :             if (cancel_pressed)
    2246           0 :                 break;
    2247          36 :             if (!opt_tuples_only)
    2248          28 :                 fprintf(fout,
    2249             :                         "2+^|Record %lu\n",
    2250             :                         record++);
    2251             :             else
    2252           8 :                 fputs("2+|\n", fout);
    2253             :         }
    2254             : 
    2255         140 :         fputs("<l|", fout);
    2256         140 :         asciidoc_escaped_print(cont->headers[i % cont->ncolumns], fout);
    2257             : 
    2258         140 :         fprintf(fout, " %s|", cont->aligns[i % cont->ncolumns] == 'r' ? ">l" : "<l");
    2259             :         /* is string only whitespace? */
    2260         140 :         if ((*ptr)[strspn(*ptr, " \t")] == '\0')
    2261          24 :             fputs(" ", fout);
    2262             :         else
    2263         116 :             asciidoc_escaped_print(*ptr, fout);
    2264         140 :         fputs("\n", fout);
    2265             :     }
    2266             : 
    2267          20 :     fputs("|====\n", fout);
    2268             : 
    2269          20 :     if (cont->opt->stop_table)
    2270             :     {
    2271             :         /* print footers */
    2272          20 :         if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
    2273             :         {
    2274             :             printTableFooter *f;
    2275             : 
    2276           4 :             fputs("\n....\n", fout);
    2277           8 :             for (f = cont->footers; f; f = f->next)
    2278             :             {
    2279           4 :                 fputs(f->data, fout);
    2280           4 :                 fputs("\n", fout);
    2281             :             }
    2282           4 :             fputs("....\n", fout);
    2283             :         }
    2284             :     }
    2285             : }
    2286             : 
    2287             : 
    2288             : /*************************/
    2289             : /* LaTeX                 */
    2290             : /*************************/
    2291             : 
    2292             : 
    2293             : static void
    2294        1636 : latex_escaped_print(const char *in, FILE *fout)
    2295             : {
    2296             :     const char *p;
    2297             : 
    2298       14376 :     for (p = in; *p; p++)
    2299       12740 :         switch (*p)
    2300             :         {
    2301             :                 /*
    2302             :                  * We convert ASCII characters per the recommendations in
    2303             :                  * Scott Pakin's "The Comprehensive LATEX Symbol List",
    2304             :                  * available from CTAN.  For non-ASCII, you're on your own.
    2305             :                  */
    2306         144 :             case '#':
    2307         144 :                 fputs("\\#", fout);
    2308         144 :                 break;
    2309         128 :             case '$':
    2310         128 :                 fputs("\\$", fout);
    2311         128 :                 break;
    2312         144 :             case '%':
    2313         144 :                 fputs("\\%", fout);
    2314         144 :                 break;
    2315         144 :             case '&':
    2316         144 :                 fputs("\\&", fout);
    2317         144 :                 break;
    2318         144 :             case '<':
    2319         144 :                 fputs("\\textless{}", fout);
    2320         144 :                 break;
    2321         144 :             case '>':
    2322         144 :                 fputs("\\textgreater{}", fout);
    2323         144 :                 break;
    2324         144 :             case '\\':
    2325         144 :                 fputs("\\textbackslash{}", fout);
    2326         144 :                 break;
    2327         144 :             case '^':
    2328         144 :                 fputs("\\^{}", fout);
    2329         144 :                 break;
    2330         312 :             case '_':
    2331         312 :                 fputs("\\_", fout);
    2332         312 :                 break;
    2333         144 :             case '{':
    2334         144 :                 fputs("\\{", fout);
    2335         144 :                 break;
    2336         144 :             case '|':
    2337         144 :                 fputs("\\textbar{}", fout);
    2338         144 :                 break;
    2339         144 :             case '}':
    2340         144 :                 fputs("\\}", fout);
    2341         144 :                 break;
    2342         144 :             case '~':
    2343         144 :                 fputs("\\~{}", fout);
    2344         144 :                 break;
    2345         144 :             case '\n':
    2346             :                 /* This is not right, but doing it right seems too hard */
    2347         144 :                 fputs("\\\\", fout);
    2348         144 :                 break;
    2349       10572 :             default:
    2350       10572 :                 fputc(*p, fout);
    2351             :         }
    2352        1636 : }
    2353             : 
    2354             : 
    2355             : static void
    2356          24 : print_latex_text(const printTableContent *cont, FILE *fout)
    2357             : {
    2358          24 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2359          24 :     unsigned short opt_border = cont->opt->border;
    2360             :     unsigned int i;
    2361             :     const char *const *ptr;
    2362             : 
    2363          24 :     if (cancel_pressed)
    2364           0 :         return;
    2365             : 
    2366          24 :     if (opt_border > 3)
    2367           0 :         opt_border = 3;
    2368             : 
    2369          24 :     if (cont->opt->start_table)
    2370             :     {
    2371             :         /* print title */
    2372          24 :         if (!opt_tuples_only && cont->title)
    2373             :         {
    2374           4 :             fputs("\\begin{center}\n", fout);
    2375           4 :             latex_escaped_print(cont->title, fout);
    2376           4 :             fputs("\n\\end{center}\n\n", fout);
    2377             :         }
    2378             : 
    2379             :         /* begin environment and set alignments and borders */
    2380          24 :         fputs("\\begin{tabular}{", fout);
    2381             : 
    2382          24 :         if (opt_border >= 2)
    2383           8 :             fputs("| ", fout);
    2384         136 :         for (i = 0; i < cont->ncolumns; i++)
    2385             :         {
    2386         112 :             fputc(*(cont->aligns + i), fout);
    2387         112 :             if (opt_border != 0 && i < cont->ncolumns - 1)
    2388          76 :                 fputs(" | ", fout);
    2389             :         }
    2390          24 :         if (opt_border >= 2)
    2391           8 :             fputs(" |", fout);
    2392             : 
    2393          24 :         fputs("}\n", fout);
    2394             : 
    2395          24 :         if (!opt_tuples_only && opt_border >= 2)
    2396           8 :             fputs("\\hline\n", fout);
    2397             : 
    2398             :         /* print headers */
    2399          24 :         if (!opt_tuples_only)
    2400             :         {
    2401         112 :             for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
    2402             :             {
    2403          92 :                 if (i != 0)
    2404          72 :                     fputs(" & ", fout);
    2405          92 :                 fputs("\\textit{", fout);
    2406          92 :                 latex_escaped_print(*ptr, fout);
    2407          92 :                 fputc('}', fout);
    2408             :             }
    2409          20 :             fputs(" \\\\\n", fout);
    2410          20 :             fputs("\\hline\n", fout);
    2411             :         }
    2412             :     }
    2413             : 
    2414             :     /* print cells */
    2415         220 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2416             :     {
    2417         196 :         latex_escaped_print(*ptr, fout);
    2418             : 
    2419         196 :         if ((i + 1) % cont->ncolumns == 0)
    2420             :         {
    2421          44 :             fputs(" \\\\\n", fout);
    2422          44 :             if (opt_border == 3)
    2423           8 :                 fputs("\\hline\n", fout);
    2424          44 :             if (cancel_pressed)
    2425           0 :                 break;
    2426             :         }
    2427             :         else
    2428         152 :             fputs(" & ", fout);
    2429             :     }
    2430             : 
    2431          24 :     if (cont->opt->stop_table)
    2432             :     {
    2433          24 :         printTableFooter *footers = footers_with_default(cont);
    2434             : 
    2435          24 :         if (opt_border == 2)
    2436           4 :             fputs("\\hline\n", fout);
    2437             : 
    2438          24 :         fputs("\\end{tabular}\n\n\\noindent ", fout);
    2439             : 
    2440             :         /* print footers */
    2441          24 :         if (footers && !opt_tuples_only && !cancel_pressed)
    2442             :         {
    2443             :             printTableFooter *f;
    2444             : 
    2445          40 :             for (f = footers; f; f = f->next)
    2446             :             {
    2447          20 :                 latex_escaped_print(f->data, fout);
    2448          20 :                 fputs(" \\\\\n", fout);
    2449             :             }
    2450             :         }
    2451             : 
    2452          24 :         fputc('\n', fout);
    2453             :     }
    2454             : }
    2455             : 
    2456             : 
    2457             : /*************************/
    2458             : /* LaTeX longtable       */
    2459             : /*************************/
    2460             : 
    2461             : 
    2462             : static void
    2463          28 : print_latex_longtable_text(const printTableContent *cont, FILE *fout)
    2464             : {
    2465          28 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2466          28 :     unsigned short opt_border = cont->opt->border;
    2467             :     unsigned int i;
    2468          28 :     const char *opt_table_attr = cont->opt->tableAttr;
    2469          28 :     const char *next_opt_table_attr_char = opt_table_attr;
    2470          28 :     const char *last_opt_table_attr_char = NULL;
    2471             :     const char *const *ptr;
    2472             : 
    2473          28 :     if (cancel_pressed)
    2474           0 :         return;
    2475             : 
    2476          28 :     if (opt_border > 3)
    2477           0 :         opt_border = 3;
    2478             : 
    2479          28 :     if (cont->opt->start_table)
    2480             :     {
    2481             :         /* begin environment and set alignments and borders */
    2482          28 :         fputs("\\begin{longtable}{", fout);
    2483             : 
    2484          28 :         if (opt_border >= 2)
    2485          12 :             fputs("| ", fout);
    2486             : 
    2487         156 :         for (i = 0; i < cont->ncolumns; i++)
    2488             :         {
    2489             :             /* longtable supports either a width (p) or an alignment (l/r) */
    2490             :             /* Are we left-justified and was a proportional width specified? */
    2491         128 :             if (*(cont->aligns + i) == 'l' && opt_table_attr)
    2492             :             {
    2493             : #define LONGTABLE_WHITESPACE    " \t\n"
    2494             : 
    2495             :                 /* advance over whitespace */
    2496          12 :                 next_opt_table_attr_char += strspn(next_opt_table_attr_char,
    2497             :                                                    LONGTABLE_WHITESPACE);
    2498             :                 /* We have a value? */
    2499          24 :                 if (next_opt_table_attr_char[0] != '\0')
    2500             :                 {
    2501           4 :                     fputs("p{", fout);
    2502           4 :                     fwrite(next_opt_table_attr_char, strcspn(next_opt_table_attr_char,
    2503             :                                                              LONGTABLE_WHITESPACE), 1, fout);
    2504           4 :                     last_opt_table_attr_char = next_opt_table_attr_char;
    2505           4 :                     next_opt_table_attr_char += strcspn(next_opt_table_attr_char,
    2506             :                                                         LONGTABLE_WHITESPACE);
    2507           4 :                     fputs("\\textwidth}", fout);
    2508             :                 }
    2509             :                 /* use previous value */
    2510           8 :                 else if (last_opt_table_attr_char != NULL)
    2511             :                 {
    2512           8 :                     fputs("p{", fout);
    2513           8 :                     fwrite(last_opt_table_attr_char, strcspn(last_opt_table_attr_char,
    2514             :                                                              LONGTABLE_WHITESPACE), 1, fout);
    2515           8 :                     fputs("\\textwidth}", fout);
    2516             :                 }
    2517             :                 else
    2518           0 :                     fputc('l', fout);
    2519             :             }
    2520             :             else
    2521         116 :                 fputc(*(cont->aligns + i), fout);
    2522             : 
    2523         128 :             if (opt_border != 0 && i < cont->ncolumns - 1)
    2524          88 :                 fputs(" | ", fout);
    2525             :         }
    2526             : 
    2527          28 :         if (opt_border >= 2)
    2528          12 :             fputs(" |", fout);
    2529             : 
    2530          28 :         fputs("}\n", fout);
    2531             : 
    2532             :         /* print headers */
    2533          28 :         if (!opt_tuples_only)
    2534             :         {
    2535             :             /* firsthead */
    2536          24 :             if (opt_border >= 2)
    2537          12 :                 fputs("\\toprule\n", fout);
    2538         132 :             for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
    2539             :             {
    2540         108 :                 if (i != 0)
    2541          84 :                     fputs(" & ", fout);
    2542         108 :                 fputs("\\small\\textbf{\\textit{", fout);
    2543         108 :                 latex_escaped_print(*ptr, fout);
    2544         108 :                 fputs("}}", fout);
    2545             :             }
    2546          24 :             fputs(" \\\\\n", fout);
    2547          24 :             fputs("\\midrule\n\\endfirsthead\n", fout);
    2548             : 
    2549             :             /* secondary heads */
    2550          24 :             if (opt_border >= 2)
    2551          12 :                 fputs("\\toprule\n", fout);
    2552         132 :             for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
    2553             :             {
    2554         108 :                 if (i != 0)
    2555          84 :                     fputs(" & ", fout);
    2556         108 :                 fputs("\\small\\textbf{\\textit{", fout);
    2557         108 :                 latex_escaped_print(*ptr, fout);
    2558         108 :                 fputs("}}", fout);
    2559             :             }
    2560          24 :             fputs(" \\\\\n", fout);
    2561             :             /* If the line under the row already appeared, don't do another */
    2562          24 :             if (opt_border != 3)
    2563          16 :                 fputs("\\midrule\n", fout);
    2564          24 :             fputs("\\endhead\n", fout);
    2565             : 
    2566             :             /* table name, caption? */
    2567          24 :             if (!opt_tuples_only && cont->title)
    2568             :             {
    2569             :                 /* Don't output if we are printing a line under each row */
    2570           4 :                 if (opt_border == 2)
    2571           0 :                     fputs("\\bottomrule\n", fout);
    2572           4 :                 fputs("\\caption[", fout);
    2573           4 :                 latex_escaped_print(cont->title, fout);
    2574           4 :                 fputs(" (Continued)]{", fout);
    2575           4 :                 latex_escaped_print(cont->title, fout);
    2576           4 :                 fputs("}\n\\endfoot\n", fout);
    2577           4 :                 if (opt_border == 2)
    2578           0 :                     fputs("\\bottomrule\n", fout);
    2579           4 :                 fputs("\\caption[", fout);
    2580           4 :                 latex_escaped_print(cont->title, fout);
    2581           4 :                 fputs("]{", fout);
    2582           4 :                 latex_escaped_print(cont->title, fout);
    2583           4 :                 fputs("}\n\\endlastfoot\n", fout);
    2584             :             }
    2585             :             /* output bottom table line? */
    2586          20 :             else if (opt_border >= 2)
    2587             :             {
    2588          12 :                 fputs("\\bottomrule\n\\endfoot\n", fout);
    2589          12 :                 fputs("\\bottomrule\n\\endlastfoot\n", fout);
    2590             :             }
    2591             :         }
    2592             :     }
    2593             : 
    2594             :     /* print cells */
    2595         256 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2596             :     {
    2597             :         /* Add a line under each row? */
    2598         228 :         if (i != 0 && i % cont->ncolumns != 0)
    2599         176 :             fputs("\n&\n", fout);
    2600         228 :         fputs("\\raggedright{", fout);
    2601         228 :         latex_escaped_print(*ptr, fout);
    2602         228 :         fputc('}', fout);
    2603         228 :         if ((i + 1) % cont->ncolumns == 0)
    2604             :         {
    2605          52 :             fputs(" \\tabularnewline\n", fout);
    2606          52 :             if (opt_border == 3)
    2607          16 :                 fputs(" \\hline\n", fout);
    2608             :         }
    2609         228 :         if (cancel_pressed)
    2610           0 :             break;
    2611             :     }
    2612             : 
    2613          28 :     if (cont->opt->stop_table)
    2614          28 :         fputs("\\end{longtable}\n", fout);
    2615             : }
    2616             : 
    2617             : 
    2618             : static void
    2619          52 : print_latex_vertical(const printTableContent *cont, FILE *fout)
    2620             : {
    2621          52 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2622          52 :     unsigned short opt_border = cont->opt->border;
    2623          52 :     unsigned long record = cont->opt->prior_records + 1;
    2624             :     unsigned int i;
    2625             :     const char *const *ptr;
    2626             : 
    2627          52 :     if (cancel_pressed)
    2628           0 :         return;
    2629             : 
    2630          52 :     if (opt_border > 2)
    2631          12 :         opt_border = 2;
    2632             : 
    2633          52 :     if (cont->opt->start_table)
    2634             :     {
    2635             :         /* print title */
    2636          52 :         if (!opt_tuples_only && cont->title)
    2637             :         {
    2638           8 :             fputs("\\begin{center}\n", fout);
    2639           8 :             latex_escaped_print(cont->title, fout);
    2640           8 :             fputs("\n\\end{center}\n\n", fout);
    2641             :         }
    2642             : 
    2643             :         /* begin environment and set alignments and borders */
    2644          52 :         fputs("\\begin{tabular}{", fout);
    2645          52 :         if (opt_border == 0)
    2646           8 :             fputs("cl", fout);
    2647          44 :         else if (opt_border == 1)
    2648          24 :             fputs("c|l", fout);
    2649          20 :         else if (opt_border == 2)
    2650          20 :             fputs("|c|l|", fout);
    2651          52 :         fputs("}\n", fout);
    2652             :     }
    2653             : 
    2654             :     /* print records */
    2655         476 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2656             :     {
    2657             :         /* new record */
    2658         424 :         if (i % cont->ncolumns == 0)
    2659             :         {
    2660          96 :             if (cancel_pressed)
    2661           0 :                 break;
    2662          96 :             if (!opt_tuples_only)
    2663             :             {
    2664          80 :                 if (opt_border == 2)
    2665             :                 {
    2666          40 :                     fputs("\\hline\n", fout);
    2667          40 :                     fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++);
    2668             :                 }
    2669             :                 else
    2670          40 :                     fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %lu}} \\\\\n", record++);
    2671             :             }
    2672          96 :             if (opt_border >= 1)
    2673          80 :                 fputs("\\hline\n", fout);
    2674             :         }
    2675             : 
    2676         424 :         latex_escaped_print(cont->headers[i % cont->ncolumns], fout);
    2677         424 :         fputs(" & ", fout);
    2678         424 :         latex_escaped_print(*ptr, fout);
    2679         424 :         fputs(" \\\\\n", fout);
    2680             :     }
    2681             : 
    2682          52 :     if (cont->opt->stop_table)
    2683             :     {
    2684          52 :         if (opt_border == 2)
    2685          20 :             fputs("\\hline\n", fout);
    2686             : 
    2687          52 :         fputs("\\end{tabular}\n\n\\noindent ", fout);
    2688             : 
    2689             :         /* print footers */
    2690          52 :         if (cont->footers && !opt_tuples_only && !cancel_pressed)
    2691             :         {
    2692             :             printTableFooter *f;
    2693             : 
    2694          16 :             for (f = cont->footers; f; f = f->next)
    2695             :             {
    2696           8 :                 latex_escaped_print(f->data, fout);
    2697           8 :                 fputs(" \\\\\n", fout);
    2698             :             }
    2699             :         }
    2700             : 
    2701          52 :         fputc('\n', fout);
    2702             :     }
    2703             : }
    2704             : 
    2705             : 
    2706             : /*************************/
    2707             : /* Troff -ms             */
    2708             : /*************************/
    2709             : 
    2710             : 
    2711             : static void
    2712         596 : troff_ms_escaped_print(const char *in, FILE *fout)
    2713             : {
    2714             :     const char *p;
    2715             : 
    2716        4792 :     for (p = in; *p; p++)
    2717        4196 :         switch (*p)
    2718             :         {
    2719          84 :             case '\\':
    2720          84 :                 fputs("\\(rs", fout);
    2721          84 :                 break;
    2722        4112 :             default:
    2723        4112 :                 fputc(*p, fout);
    2724             :         }
    2725         596 : }
    2726             : 
    2727             : 
    2728             : static void
    2729          20 : print_troff_ms_text(const printTableContent *cont, FILE *fout)
    2730             : {
    2731          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2732          20 :     unsigned short opt_border = cont->opt->border;
    2733             :     unsigned int i;
    2734             :     const char *const *ptr;
    2735             : 
    2736          20 :     if (cancel_pressed)
    2737           0 :         return;
    2738             : 
    2739          20 :     if (opt_border > 2)
    2740           0 :         opt_border = 2;
    2741             : 
    2742          20 :     if (cont->opt->start_table)
    2743             :     {
    2744             :         /* print title */
    2745          20 :         if (!opt_tuples_only && cont->title)
    2746             :         {
    2747           4 :             fputs(".LP\n.DS C\n", fout);
    2748           4 :             troff_ms_escaped_print(cont->title, fout);
    2749           4 :             fputs("\n.DE\n", fout);
    2750             :         }
    2751             : 
    2752             :         /* begin environment and set alignments and borders */
    2753          20 :         fputs(".LP\n.TS\n", fout);
    2754          20 :         if (opt_border == 2)
    2755           4 :             fputs("center box;\n", fout);
    2756             :         else
    2757          16 :             fputs("center;\n", fout);
    2758             : 
    2759         116 :         for (i = 0; i < cont->ncolumns; i++)
    2760             :         {
    2761          96 :             fputc(*(cont->aligns + i), fout);
    2762          96 :             if (opt_border > 0 && i < cont->ncolumns - 1)
    2763          64 :                 fputs(" | ", fout);
    2764             :         }
    2765          20 :         fputs(".\n", fout);
    2766             : 
    2767             :         /* print headers */
    2768          20 :         if (!opt_tuples_only)
    2769             :         {
    2770          92 :             for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
    2771             :             {
    2772          76 :                 if (i != 0)
    2773          60 :                     fputc('\t', fout);
    2774          76 :                 fputs("\\fI", fout);
    2775          76 :                 troff_ms_escaped_print(*ptr, fout);
    2776          76 :                 fputs("\\fP", fout);
    2777             :             }
    2778          16 :             fputs("\n_\n", fout);
    2779             :         }
    2780             :     }
    2781             : 
    2782             :     /* print cells */
    2783         184 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2784             :     {
    2785         164 :         troff_ms_escaped_print(*ptr, fout);
    2786             : 
    2787         164 :         if ((i + 1) % cont->ncolumns == 0)
    2788             :         {
    2789          36 :             fputc('\n', fout);
    2790          36 :             if (cancel_pressed)
    2791           0 :                 break;
    2792             :         }
    2793             :         else
    2794         128 :             fputc('\t', fout);
    2795             :     }
    2796             : 
    2797          20 :     if (cont->opt->stop_table)
    2798             :     {
    2799          20 :         printTableFooter *footers = footers_with_default(cont);
    2800             : 
    2801          20 :         fputs(".TE\n.DS L\n", fout);
    2802             : 
    2803             :         /* print footers */
    2804          20 :         if (footers && !opt_tuples_only && !cancel_pressed)
    2805             :         {
    2806             :             printTableFooter *f;
    2807             : 
    2808          32 :             for (f = footers; f; f = f->next)
    2809             :             {
    2810          16 :                 troff_ms_escaped_print(f->data, fout);
    2811          16 :                 fputc('\n', fout);
    2812             :             }
    2813             :         }
    2814             : 
    2815          20 :         fputs(".DE\n", fout);
    2816             :     }
    2817             : }
    2818             : 
    2819             : 
    2820             : static void
    2821          20 : print_troff_ms_vertical(const printTableContent *cont, FILE *fout)
    2822             : {
    2823          20 :     bool        opt_tuples_only = cont->opt->tuples_only;
    2824          20 :     unsigned short opt_border = cont->opt->border;
    2825          20 :     unsigned long record = cont->opt->prior_records + 1;
    2826             :     unsigned int i;
    2827             :     const char *const *ptr;
    2828          20 :     unsigned short current_format = 0;  /* 0=none, 1=header, 2=body */
    2829             : 
    2830          20 :     if (cancel_pressed)
    2831           0 :         return;
    2832             : 
    2833          20 :     if (opt_border > 2)
    2834           0 :         opt_border = 2;
    2835             : 
    2836          20 :     if (cont->opt->start_table)
    2837             :     {
    2838             :         /* print title */
    2839          20 :         if (!opt_tuples_only && cont->title)
    2840             :         {
    2841           4 :             fputs(".LP\n.DS C\n", fout);
    2842           4 :             troff_ms_escaped_print(cont->title, fout);
    2843           4 :             fputs("\n.DE\n", fout);
    2844             :         }
    2845             : 
    2846             :         /* begin environment and set alignments and borders */
    2847          20 :         fputs(".LP\n.TS\n", fout);
    2848          20 :         if (opt_border == 2)
    2849           4 :             fputs("center box;\n", fout);
    2850             :         else
    2851          16 :             fputs("center;\n", fout);
    2852             : 
    2853             :         /* basic format */
    2854          20 :         if (opt_tuples_only)
    2855           4 :             fputs("c l;\n", fout);
    2856             :     }
    2857             :     else
    2858           0 :         current_format = 2;     /* assume tuples printed already */
    2859             : 
    2860             :     /* print records */
    2861         184 :     for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
    2862             :     {
    2863             :         /* new record */
    2864         164 :         if (i % cont->ncolumns == 0)
    2865             :         {
    2866          36 :             if (cancel_pressed)
    2867           0 :                 break;
    2868          36 :             if (!opt_tuples_only)
    2869             :             {
    2870          28 :                 if (current_format != 1)
    2871             :                 {
    2872          28 :                     if (opt_border == 2 && record > 1)
    2873           4 :                         fputs("_\n", fout);
    2874          28 :                     if (current_format != 0)
    2875          12 :                         fputs(".T&\n", fout);
    2876          28 :                     fputs("c s.\n", fout);
    2877          28 :                     current_format = 1;
    2878             :                 }
    2879          28 :                 fprintf(fout, "\\fIRecord %lu\\fP\n", record++);
    2880             :             }
    2881          36 :             if (opt_border >= 1)
    2882          28 :                 fputs("_\n", fout);
    2883             :         }
    2884             : 
    2885         164 :         if (!opt_tuples_only)
    2886             :         {
    2887         124 :             if (current_format != 2)
    2888             :             {
    2889          28 :                 if (current_format != 0)
    2890          28 :                     fputs(".T&\n", fout);
    2891          28 :                 if (opt_border != 1)
    2892          16 :                     fputs("c l.\n", fout);
    2893             :                 else
    2894          12 :                     fputs("c | l.\n", fout);
    2895          28 :                 current_format = 2;
    2896             :             }
    2897             :         }
    2898             : 
    2899         164 :         troff_ms_escaped_print(cont->headers[i % cont->ncolumns], fout);
    2900         164 :         fputc('\t', fout);
    2901         164 :         troff_ms_escaped_print(*ptr, fout);
    2902             : 
    2903         164 :         fputc('\n', fout);
    2904             :     }
    2905             : 
    2906          20 :     if (cont->opt->stop_table)
    2907             :     {
    2908          20 :         fputs(".TE\n.DS L\n", fout);
    2909             : 
    2910             :         /* print footers */
    2911          20 :         if (cont->footers && !opt_tuples_only && !cancel_pressed)
    2912             :         {
    2913             :             printTableFooter *f;
    2914             : 
    2915           8 :             for (f = cont->footers; f; f = f->next)
    2916             :             {
    2917           4 :                 troff_ms_escaped_print(f->data, fout);
    2918           4 :                 fputc('\n', fout);
    2919             :             }
    2920             :         }
    2921             : 
    2922          20 :         fputs(".DE\n", fout);
    2923             :     }
    2924             : }
    2925             : 
    2926             : 
    2927             : /********************************/
    2928             : /* Public functions             */
    2929             : /********************************/
    2930             : 
    2931             : 
    2932             : /*
    2933             :  * disable_sigpipe_trap
    2934             :  *
    2935             :  * Turn off SIGPIPE interrupt --- call this before writing to a temporary
    2936             :  * query output file that is a pipe.
    2937             :  *
    2938             :  * No-op on Windows, where there's no SIGPIPE interrupts.
    2939             :  */
    2940             : void
    2941           0 : disable_sigpipe_trap(void)
    2942             : {
    2943             : #ifndef WIN32
    2944           0 :     pqsignal(SIGPIPE, SIG_IGN);
    2945             : #endif
    2946           0 : }
    2947             : 
    2948             : /*
    2949             :  * restore_sigpipe_trap
    2950             :  *
    2951             :  * Restore normal SIGPIPE interrupt --- call this when done writing to a
    2952             :  * temporary query output file that was (or might have been) a pipe.
    2953             :  *
    2954             :  * Note: within psql, we enable SIGPIPE interrupts unless the permanent query
    2955             :  * output file is a pipe, in which case they should be kept off.  This
    2956             :  * approach works only because psql is not currently complicated enough to
    2957             :  * have nested usages of short-lived output files.  Otherwise we'd probably
    2958             :  * need a genuine save-and-restore-state approach; but for now, that would be
    2959             :  * useless complication.  In non-psql programs, this always enables SIGPIPE.
    2960             :  *
    2961             :  * No-op on Windows, where there's no SIGPIPE interrupts.
    2962             :  */
    2963             : void
    2964        4654 : restore_sigpipe_trap(void)
    2965             : {
    2966             : #ifndef WIN32
    2967        4654 :     pqsignal(SIGPIPE, always_ignore_sigpipe ? SIG_IGN : SIG_DFL);
    2968             : #endif
    2969        4654 : }
    2970             : 
    2971             : /*
    2972             :  * set_sigpipe_trap_state
    2973             :  *
    2974             :  * Set the trap state that restore_sigpipe_trap should restore to.
    2975             :  */
    2976             : void
    2977        4654 : set_sigpipe_trap_state(bool ignore)
    2978             : {
    2979        4654 :     always_ignore_sigpipe = ignore;
    2980        4654 : }
    2981             : 
    2982             : 
    2983             : /*
    2984             :  * PageOutput
    2985             :  *
    2986             :  * Tests if pager is needed and returns appropriate FILE pointer.
    2987             :  *
    2988             :  * If the topt argument is NULL no pager is used.
    2989             :  */
    2990             : FILE *
    2991       63182 : PageOutput(int lines, const printTableOpt *topt)
    2992             : {
    2993             :     /* check whether we need / can / are supposed to use pager */
    2994       63182 :     if (topt && topt->pager && isatty(fileno(stdin)) && isatty(fileno(stdout)))
    2995             :     {
    2996             : #ifdef TIOCGWINSZ
    2997           0 :         unsigned short int pager = topt->pager;
    2998           0 :         int         min_lines = topt->pager_min_lines;
    2999             :         int         result;
    3000             :         struct winsize screen_size;
    3001             : 
    3002           0 :         result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
    3003             : 
    3004             :         /* >= accounts for a one-line prompt */
    3005           0 :         if (result == -1
    3006           0 :             || (lines >= screen_size.ws_row && lines >= min_lines)
    3007           0 :             || pager > 1)
    3008             : #endif
    3009             :         {
    3010             :             const char *pagerprog;
    3011             :             FILE       *pagerpipe;
    3012             : 
    3013           0 :             pagerprog = getenv("PSQL_PAGER");
    3014           0 :             if (!pagerprog)
    3015           0 :                 pagerprog = getenv("PAGER");
    3016           0 :             if (!pagerprog)
    3017           0 :                 pagerprog = DEFAULT_PAGER;
    3018             :             else
    3019             :             {
    3020             :                 /* if PAGER is empty or all-white-space, don't use pager */
    3021           0 :                 if (strspn(pagerprog, " \t\r\n") == strlen(pagerprog))
    3022           0 :                     return stdout;
    3023             :             }
    3024           0 :             disable_sigpipe_trap();
    3025           0 :             pagerpipe = popen(pagerprog, "w");
    3026           0 :             if (pagerpipe)
    3027           0 :                 return pagerpipe;
    3028             :             /* if popen fails, silently proceed without pager */
    3029           0 :             restore_sigpipe_trap();
    3030             :         }
    3031             :     }
    3032             : 
    3033       63182 :     return stdout;
    3034             : }
    3035             : 
    3036             : /*
    3037             :  * ClosePager
    3038             :  *
    3039             :  * Close previously opened pager pipe, if any
    3040             :  */
    3041             : void
    3042         188 : ClosePager(FILE *pagerpipe)
    3043             : {
    3044         188 :     if (pagerpipe && pagerpipe != stdout)
    3045             :     {
    3046             :         /*
    3047             :          * If printing was canceled midstream, warn about it.
    3048             :          *
    3049             :          * Some pagers like less use Ctrl-C as part of their command set. Even
    3050             :          * so, we abort our processing and warn the user what we did.  If the
    3051             :          * pager quit as a result of the SIGINT, this message won't go
    3052             :          * anywhere ...
    3053             :          */
    3054           0 :         if (cancel_pressed)
    3055           0 :             fprintf(pagerpipe, _("Interrupted\n"));
    3056             : 
    3057           0 :         pclose(pagerpipe);
    3058           0 :         restore_sigpipe_trap();
    3059             :     }
    3060         188 : }
    3061             : 
    3062             : /*
    3063             :  * Initialise a table contents struct.
    3064             :  *      Must be called before any other printTable method is used.
    3065             :  *
    3066             :  * The title is not duplicated; the caller must ensure that the buffer
    3067             :  * is available for the lifetime of the printTableContent struct.
    3068             :  *
    3069             :  * If you call this, you must call printTableCleanup once you're done with the
    3070             :  * table.
    3071             :  */
    3072             : void
    3073       63238 : printTableInit(printTableContent *const content, const printTableOpt *opt,
    3074             :                const char *title, const int ncolumns, const int nrows)
    3075             : {
    3076       63238 :     content->opt = opt;
    3077       63238 :     content->title = title;
    3078       63238 :     content->ncolumns = ncolumns;
    3079       63238 :     content->nrows = nrows;
    3080             : 
    3081       63238 :     content->headers = pg_malloc0((ncolumns + 1) * sizeof(*content->headers));
    3082             : 
    3083       63238 :     content->cells = pg_malloc0((ncolumns * nrows + 1) * sizeof(*content->cells));
    3084             : 
    3085       63238 :     content->cellmustfree = NULL;
    3086       63238 :     content->footers = NULL;
    3087             : 
    3088       63238 :     content->aligns = pg_malloc0((ncolumns + 1) * sizeof(*content->align));
    3089             : 
    3090       63238 :     content->header = content->headers;
    3091       63238 :     content->cell = content->cells;
    3092       63238 :     content->footer = content->footers;
    3093       63238 :     content->align = content->aligns;
    3094       63238 :     content->cellsadded = 0;
    3095       63238 : }
    3096             : 
    3097             : /*
    3098             :  * Add a header to the table.
    3099             :  *
    3100             :  * Headers are not duplicated; you must ensure that the header string is
    3101             :  * available for the lifetime of the printTableContent struct.
    3102             :  *
    3103             :  * If translate is true, the function will pass the header through gettext.
    3104             :  * Otherwise, the header will not be translated.
    3105             :  *
    3106             :  * align is either 'l' or 'r', and specifies the alignment for cells in this
    3107             :  * column.
    3108             :  */
    3109             : void
    3110      120704 : printTableAddHeader(printTableContent *const content, char *header,
    3111             :                     const bool translate, const char align)
    3112             : {
    3113             : #ifndef ENABLE_NLS
    3114             :     (void) translate;           /* unused parameter */
    3115             : #endif
    3116             : 
    3117      120704 :     if (content->header >= content->headers + content->ncolumns)
    3118             :     {
    3119           0 :         fprintf(stderr, _("Cannot add header to table content: "
    3120             :                           "column count of %d exceeded.\n"),
    3121             :                 content->ncolumns);
    3122           0 :         exit(EXIT_FAILURE);
    3123             :     }
    3124             : 
    3125      241408 :     *content->header = (char *) mbvalidate((unsigned char *) header,
    3126      120704 :                                            content->opt->encoding);
    3127             : #ifdef ENABLE_NLS
    3128      120704 :     if (translate)
    3129       13594 :         *content->header = _(*content->header);
    3130             : #endif
    3131      120704 :     content->header++;
    3132             : 
    3133      120704 :     *content->align = align;
    3134      120704 :     content->align++;
    3135      120704 : }
    3136             : 
    3137             : /*
    3138             :  * Add a cell to the table.
    3139             :  *
    3140             :  * Cells are not duplicated; you must ensure that the cell string is available
    3141             :  * for the lifetime of the printTableContent struct.
    3142             :  *
    3143             :  * If translate is true, the function will pass the cell through gettext.
    3144             :  * Otherwise, the cell will not be translated.
    3145             :  *
    3146             :  * If mustfree is true, the cell string is freed by printTableCleanup().
    3147             :  * Note: Automatic freeing of translatable strings is not supported.
    3148             :  */
    3149             : void
    3150      632894 : printTableAddCell(printTableContent *const content, char *cell,
    3151             :                   const bool translate, const bool mustfree)
    3152             : {
    3153             : #ifndef ENABLE_NLS
    3154             :     (void) translate;           /* unused parameter */
    3155             : #endif
    3156             : 
    3157      632894 :     if (content->cellsadded >= content->ncolumns * content->nrows)
    3158             :     {
    3159           0 :         fprintf(stderr, _("Cannot add cell to table content: "
    3160             :                           "total cell count of %d exceeded.\n"),
    3161           0 :                 content->ncolumns * content->nrows);
    3162           0 :         exit(EXIT_FAILURE);
    3163             :     }
    3164             : 
    3165     1265788 :     *content->cell = (char *) mbvalidate((unsigned char *) cell,
    3166      632894 :                                          content->opt->encoding);
    3167             : 
    3168             : #ifdef ENABLE_NLS
    3169      632894 :     if (translate)
    3170         700 :         *content->cell = _(*content->cell);
    3171             : #endif
    3172             : 
    3173      632894 :     if (mustfree)
    3174             :     {
    3175         140 :         if (content->cellmustfree == NULL)
    3176          72 :             content->cellmustfree =
    3177          72 :                 pg_malloc0((content->ncolumns * content->nrows + 1) * sizeof(bool));
    3178             : 
    3179         140 :         content->cellmustfree[content->cellsadded] = true;
    3180             :     }
    3181      632894 :     content->cell++;
    3182      632894 :     content->cellsadded++;
    3183      632894 : }
    3184             : 
    3185             : /*
    3186             :  * Add a footer to the table.
    3187             :  *
    3188             :  * Footers are added as elements of a singly-linked list, and the content is
    3189             :  * strdup'd, so there is no need to keep the original footer string around.
    3190             :  *
    3191             :  * Footers are never translated by the function.  If you want the footer
    3192             :  * translated you must do so yourself, before calling printTableAddFooter.  The
    3193             :  * reason this works differently to headers and cells is that footers tend to
    3194             :  * be made of up individually translated components, rather than being
    3195             :  * translated as a whole.
    3196             :  */
    3197             : void
    3198        4650 : printTableAddFooter(printTableContent *const content, const char *footer)
    3199             : {
    3200             :     printTableFooter *f;
    3201             : 
    3202        4650 :     f = pg_malloc0(sizeof(*f));
    3203        4650 :     f->data = pg_strdup(footer);
    3204             : 
    3205        4650 :     if (content->footers == NULL)
    3206        1624 :         content->footers = f;
    3207             :     else
    3208        3026 :         content->footer->next = f;
    3209             : 
    3210        4650 :     content->footer = f;
    3211        4650 : }
    3212             : 
    3213             : /*
    3214             :  * Change the content of the last-added footer.
    3215             :  *
    3216             :  * The current contents of the last-added footer are freed, and replaced by the
    3217             :  * content given in *footer.  If there was no previous footer, add a new one.
    3218             :  *
    3219             :  * The content is strdup'd, so there is no need to keep the original string
    3220             :  * around.
    3221             :  */
    3222             : void
    3223          20 : printTableSetFooter(printTableContent *const content, const char *footer)
    3224             : {
    3225          20 :     if (content->footers != NULL)
    3226             :     {
    3227          20 :         free(content->footer->data);
    3228          20 :         content->footer->data = pg_strdup(footer);
    3229             :     }
    3230             :     else
    3231           0 :         printTableAddFooter(content, footer);
    3232          20 : }
    3233             : 
    3234             : /*
    3235             :  * Free all memory allocated to this struct.
    3236             :  *
    3237             :  * Once this has been called, the struct is unusable unless you pass it to
    3238             :  * printTableInit() again.
    3239             :  */
    3240             : void
    3241       63238 : printTableCleanup(printTableContent *const content)
    3242             : {
    3243       63238 :     if (content->cellmustfree)
    3244             :     {
    3245             :         int         i;
    3246             : 
    3247        1056 :         for (i = 0; i < content->nrows * content->ncolumns; i++)
    3248             :         {
    3249         984 :             if (content->cellmustfree[i])
    3250         140 :                 free(unconstify(char *, content->cells[i]));
    3251             :         }
    3252          72 :         free(content->cellmustfree);
    3253          72 :         content->cellmustfree = NULL;
    3254             :     }
    3255       63238 :     free(content->headers);
    3256       63238 :     free(content->cells);
    3257       63238 :     free(content->aligns);
    3258             : 
    3259       63238 :     content->opt = NULL;
    3260       63238 :     content->title = NULL;
    3261       63238 :     content->headers = NULL;
    3262       63238 :     content->cells = NULL;
    3263       63238 :     content->aligns = NULL;
    3264       63238 :     content->header = NULL;
    3265       63238 :     content->cell = NULL;
    3266       63238 :     content->align = NULL;
    3267             : 
    3268       63238 :     if (content->footers)
    3269             :     {
    3270        6274 :         for (content->footer = content->footers; content->footer;)
    3271             :         {
    3272             :             printTableFooter *f;
    3273             : 
    3274        4650 :             f = content->footer;
    3275        4650 :             content->footer = f->next;
    3276        4650 :             free(f->data);
    3277        4650 :             free(f);
    3278             :         }
    3279             :     }
    3280       63238 :     content->footers = NULL;
    3281       63238 :     content->footer = NULL;
    3282       63238 : }
    3283             : 
    3284             : /*
    3285             :  * IsPagerNeeded
    3286             :  *
    3287             :  * Setup pager if required
    3288             :  */
    3289             : static void
    3290       62994 : IsPagerNeeded(const printTableContent *cont, int extra_lines, bool expanded,
    3291             :               FILE **fout, bool *is_pager)
    3292             : {
    3293       62994 :     if (*fout == stdout)
    3294             :     {
    3295             :         int         lines;
    3296             : 
    3297       62994 :         if (expanded)
    3298         530 :             lines = (cont->ncolumns + 1) * cont->nrows;
    3299             :         else
    3300       62464 :             lines = cont->nrows + 1;
    3301             : 
    3302       62994 :         if (!cont->opt->tuples_only)
    3303             :         {
    3304             :             printTableFooter *f;
    3305             : 
    3306             :             /*
    3307             :              * FIXME -- this is slightly bogus: it counts the number of
    3308             :              * footers, not the number of lines in them.
    3309             :              */
    3310       65952 :             for (f = cont->footers; f; f = f->next)
    3311        4634 :                 lines++;
    3312             :         }
    3313             : 
    3314       62994 :         *fout = PageOutput(lines + extra_lines, cont->opt);
    3315       62994 :         *is_pager = (*fout != stdout);
    3316             :     }
    3317             :     else
    3318           0 :         *is_pager = false;
    3319       62994 : }
    3320             : 
    3321             : /*
    3322             :  * Use this to print any table in the supported formats.
    3323             :  *
    3324             :  * cont: table data and formatting options
    3325             :  * fout: where to print to
    3326             :  * is_pager: true if caller has already redirected fout to be a pager pipe
    3327             :  * flog: if not null, also print the table there (for --log-file option)
    3328             :  */
    3329             : void
    3330       63234 : printTable(const printTableContent *cont,
    3331             :            FILE *fout, bool is_pager, FILE *flog)
    3332             : {
    3333       63234 :     bool        is_local_pager = false;
    3334             : 
    3335       63234 :     if (cancel_pressed)
    3336           0 :         return;
    3337             : 
    3338       63234 :     if (cont->opt->format == PRINT_NOTHING)
    3339           0 :         return;
    3340             : 
    3341             :     /* print_aligned_*() handle the pager themselves */
    3342       63234 :     if (!is_pager &&
    3343       63154 :         cont->opt->format != PRINT_ALIGNED &&
    3344        2140 :         cont->opt->format != PRINT_WRAPPED)
    3345             :     {
    3346        2000 :         IsPagerNeeded(cont, 0, (cont->opt->expanded == 1), &fout, &is_pager);
    3347        2000 :         is_local_pager = is_pager;
    3348             :     }
    3349             : 
    3350             :     /* print the stuff */
    3351             : 
    3352       63234 :     if (flog)
    3353           0 :         print_aligned_text(cont, flog, false);
    3354             : 
    3355       63234 :     switch (cont->opt->format)
    3356             :     {
    3357        1732 :         case PRINT_UNALIGNED:
    3358        1732 :             if (cont->opt->expanded == 1)
    3359          68 :                 print_unaligned_vertical(cont, fout);
    3360             :             else
    3361        1664 :                 print_unaligned_text(cont, fout);
    3362        1732 :             break;
    3363       61234 :         case PRINT_ALIGNED:
    3364             :         case PRINT_WRAPPED:
    3365             : 
    3366             :             /*
    3367             :              * In expanded-auto mode, force vertical if a pager is passed in;
    3368             :              * else we may make different decisions for different hunks of the
    3369             :              * query result.
    3370             :              */
    3371       61234 :             if (cont->opt->expanded == 1 ||
    3372       60868 :                 (cont->opt->expanded == 2 && is_pager))
    3373         366 :                 print_aligned_vertical(cont, fout, is_pager);
    3374             :             else
    3375       60868 :                 print_aligned_text(cont, fout, is_pager);
    3376       61234 :             break;
    3377          44 :         case PRINT_CSV:
    3378          44 :             if (cont->opt->expanded == 1)
    3379          12 :                 print_csv_vertical(cont, fout);
    3380             :             else
    3381          32 :                 print_csv_text(cont, fout);
    3382          44 :             break;
    3383          40 :         case PRINT_HTML:
    3384          40 :             if (cont->opt->expanded == 1)
    3385          20 :                 print_html_vertical(cont, fout);
    3386             :             else
    3387          20 :                 print_html_text(cont, fout);
    3388          40 :             break;
    3389          40 :         case PRINT_ASCIIDOC:
    3390          40 :             if (cont->opt->expanded == 1)
    3391          20 :                 print_asciidoc_vertical(cont, fout);
    3392             :             else
    3393          20 :                 print_asciidoc_text(cont, fout);
    3394          40 :             break;
    3395          48 :         case PRINT_LATEX:
    3396          48 :             if (cont->opt->expanded == 1)
    3397          24 :                 print_latex_vertical(cont, fout);
    3398             :             else
    3399          24 :                 print_latex_text(cont, fout);
    3400          48 :             break;
    3401          56 :         case PRINT_LATEX_LONGTABLE:
    3402          56 :             if (cont->opt->expanded == 1)
    3403          28 :                 print_latex_vertical(cont, fout);
    3404             :             else
    3405          28 :                 print_latex_longtable_text(cont, fout);
    3406          56 :             break;
    3407          40 :         case PRINT_TROFF_MS:
    3408          40 :             if (cont->opt->expanded == 1)
    3409          20 :                 print_troff_ms_vertical(cont, fout);
    3410             :             else
    3411          20 :                 print_troff_ms_text(cont, fout);
    3412          40 :             break;
    3413           0 :         default:
    3414           0 :             fprintf(stderr, _("invalid output format (internal error): %d"),
    3415           0 :                     cont->opt->format);
    3416           0 :             exit(EXIT_FAILURE);
    3417             :     }
    3418             : 
    3419       63234 :     if (is_local_pager)
    3420           0 :         ClosePager(fout);
    3421             : }
    3422             : 
    3423             : /*
    3424             :  * Use this to print query results
    3425             :  *
    3426             :  * result: result of a successful query
    3427             :  * opt: formatting options
    3428             :  * fout: where to print to
    3429             :  * is_pager: true if caller has already redirected fout to be a pager pipe
    3430             :  * flog: if not null, also print the data there (for --log-file option)
    3431             :  */
    3432             : void
    3433       61438 : printQuery(const PGresult *result, const printQueryOpt *opt,
    3434             :            FILE *fout, bool is_pager, FILE *flog)
    3435             : {
    3436             :     printTableContent cont;
    3437             :     int         i,
    3438             :                 r,
    3439             :                 c;
    3440             : 
    3441       61438 :     if (cancel_pressed)
    3442           0 :         return;
    3443             : 
    3444       61438 :     printTableInit(&cont, &opt->topt, opt->title,
    3445             :                    PQnfields(result), PQntuples(result));
    3446             : 
    3447             :     /* Assert caller supplied enough translate_columns[] entries */
    3448             :     Assert(opt->translate_columns == NULL ||
    3449             :            opt->n_translate_columns >= cont.ncolumns);
    3450             : 
    3451      171298 :     for (i = 0; i < cont.ncolumns; i++)
    3452             :     {
    3453      109860 :         printTableAddHeader(&cont, PQfname(result, i),
    3454      109860 :                             opt->translate_header,
    3455      109860 :                             column_type_alignment(PQftype(result, i)));
    3456             :     }
    3457             : 
    3458             :     /* set cells */
    3459      320186 :     for (r = 0; r < cont.nrows; r++)
    3460             :     {
    3461      866514 :         for (c = 0; c < cont.ncolumns; c++)
    3462             :         {
    3463             :             char       *cell;
    3464      607766 :             bool        mustfree = false;
    3465             :             bool        translate;
    3466             : 
    3467      607766 :             if (PQgetisnull(result, r, c))
    3468       28580 :                 cell = opt->nullPrint ? opt->nullPrint : "";
    3469             :             else
    3470             :             {
    3471      579186 :                 cell = PQgetvalue(result, r, c);
    3472      579186 :                 if (cont.aligns[c] == 'r' && opt->topt.numericLocale)
    3473             :                 {
    3474          64 :                     cell = format_numeric_locale(cell);
    3475          64 :                     mustfree = true;
    3476             :                 }
    3477             :             }
    3478             : 
    3479      607766 :             translate = (opt->translate_columns && opt->translate_columns[c]);
    3480      607766 :             printTableAddCell(&cont, cell, translate, mustfree);
    3481             :         }
    3482             :     }
    3483             : 
    3484             :     /* set footers */
    3485       61438 :     if (opt->footers)
    3486             :     {
    3487             :         char      **footer;
    3488             : 
    3489         196 :         for (footer = opt->footers; *footer; footer++)
    3490          96 :             printTableAddFooter(&cont, *footer);
    3491             :     }
    3492             : 
    3493       61438 :     printTable(&cont, fout, is_pager, flog);
    3494       61438 :     printTableCleanup(&cont);
    3495             : }
    3496             : 
    3497             : char
    3498      109972 : column_type_alignment(Oid ftype)
    3499             : {
    3500             :     char        align;
    3501             : 
    3502      109972 :     switch (ftype)
    3503             :     {
    3504       44182 :         case INT2OID:
    3505             :         case INT4OID:
    3506             :         case INT8OID:
    3507             :         case FLOAT4OID:
    3508             :         case FLOAT8OID:
    3509             :         case NUMERICOID:
    3510             :         case OIDOID:
    3511             :         case XIDOID:
    3512             :         case XID8OID:
    3513             :         case CIDOID:
    3514             :         case CASHOID:
    3515       44182 :             align = 'r';
    3516       44182 :             break;
    3517       65790 :         default:
    3518       65790 :             align = 'l';
    3519       65790 :             break;
    3520             :     }
    3521      109972 :     return align;
    3522             : }
    3523             : 
    3524             : void
    3525        5024 : setDecimalLocale(void)
    3526             : {
    3527             :     struct lconv *extlconv;
    3528             : 
    3529        5024 :     extlconv = localeconv();
    3530             : 
    3531             :     /* Don't accept an empty decimal_point string */
    3532        5024 :     if (*extlconv->decimal_point)
    3533        5024 :         decimal_point = pg_strdup(extlconv->decimal_point);
    3534             :     else
    3535           0 :         decimal_point = ".";  /* SQL output standard */
    3536             : 
    3537             :     /*
    3538             :      * Although the Open Group standard allows locales to supply more than one
    3539             :      * group width, we consider only the first one, and we ignore any attempt
    3540             :      * to suppress grouping by specifying CHAR_MAX.  As in the backend's
    3541             :      * cash.c, we must apply a range check to avoid being fooled by variant
    3542             :      * CHAR_MAX values.
    3543             :      */
    3544        5024 :     groupdigits = *extlconv->grouping;
    3545        5024 :     if (groupdigits <= 0 || groupdigits > 6)
    3546          18 :         groupdigits = 3;        /* most common */
    3547             : 
    3548             :     /* Don't accept an empty thousands_sep string, either */
    3549             :     /* similar code exists in formatting.c */
    3550        5024 :     if (*extlconv->thousands_sep)
    3551        5006 :         thousands_sep = pg_strdup(extlconv->thousands_sep);
    3552             :     /* Make sure thousands separator doesn't match decimal point symbol. */
    3553          18 :     else if (strcmp(decimal_point, ",") != 0)
    3554          18 :         thousands_sep = ",";
    3555             :     else
    3556           0 :         thousands_sep = ".";
    3557        5024 : }
    3558             : 
    3559             : /* get selected or default line style */
    3560             : const printTextFormat *
    3561       61238 : get_line_style(const printTableOpt *opt)
    3562             : {
    3563             :     /*
    3564             :      * Note: this function mainly exists to preserve the convention that a
    3565             :      * printTableOpt struct can be initialized to zeroes to get default
    3566             :      * behavior.
    3567             :      */
    3568       61238 :     if (opt->line_style != NULL)
    3569         440 :         return opt->line_style;
    3570             :     else
    3571       60798 :         return &pg_asciiformat;
    3572             : }
    3573             : 
    3574             : void
    3575        5024 : refresh_utf8format(const printTableOpt *opt)
    3576             : {
    3577        5024 :     printTextFormat *popt = &pg_utf8format;
    3578             : 
    3579             :     const unicodeStyleBorderFormat *border;
    3580             :     const unicodeStyleRowFormat *header;
    3581             :     const unicodeStyleColumnFormat *column;
    3582             : 
    3583        5024 :     popt->name = "unicode";
    3584             : 
    3585        5024 :     border = &unicode_style.border_style[opt->unicode_border_linestyle];
    3586        5024 :     header = &unicode_style.row_style[opt->unicode_header_linestyle];
    3587        5024 :     column = &unicode_style.column_style[opt->unicode_column_linestyle];
    3588             : 
    3589        5024 :     popt->lrule[PRINT_RULE_TOP].hrule = border->horizontal;
    3590        5024 :     popt->lrule[PRINT_RULE_TOP].leftvrule = border->down_and_right;
    3591        5024 :     popt->lrule[PRINT_RULE_TOP].midvrule = column->down_and_horizontal[opt->unicode_border_linestyle];
    3592        5024 :     popt->lrule[PRINT_RULE_TOP].rightvrule = border->down_and_left;
    3593             : 
    3594        5024 :     popt->lrule[PRINT_RULE_MIDDLE].hrule = header->horizontal;
    3595        5024 :     popt->lrule[PRINT_RULE_MIDDLE].leftvrule = header->vertical_and_right[opt->unicode_border_linestyle];
    3596        5024 :     popt->lrule[PRINT_RULE_MIDDLE].midvrule = column->vertical_and_horizontal[opt->unicode_header_linestyle];
    3597        5024 :     popt->lrule[PRINT_RULE_MIDDLE].rightvrule = header->vertical_and_left[opt->unicode_border_linestyle];
    3598             : 
    3599        5024 :     popt->lrule[PRINT_RULE_BOTTOM].hrule = border->horizontal;
    3600        5024 :     popt->lrule[PRINT_RULE_BOTTOM].leftvrule = border->up_and_right;
    3601        5024 :     popt->lrule[PRINT_RULE_BOTTOM].midvrule = column->up_and_horizontal[opt->unicode_border_linestyle];
    3602        5024 :     popt->lrule[PRINT_RULE_BOTTOM].rightvrule = border->left_and_right;
    3603             : 
    3604             :     /* N/A */
    3605        5024 :     popt->lrule[PRINT_RULE_DATA].hrule = "";
    3606        5024 :     popt->lrule[PRINT_RULE_DATA].leftvrule = border->vertical;
    3607        5024 :     popt->lrule[PRINT_RULE_DATA].midvrule = column->vertical;
    3608        5024 :     popt->lrule[PRINT_RULE_DATA].rightvrule = border->vertical;
    3609             : 
    3610        5024 :     popt->midvrule_nl = column->vertical;
    3611        5024 :     popt->midvrule_wrap = column->vertical;
    3612        5024 :     popt->midvrule_blank = column->vertical;
    3613             : 
    3614             :     /* Same for all unicode today */
    3615        5024 :     popt->header_nl_left = unicode_style.header_nl_left;
    3616        5024 :     popt->header_nl_right = unicode_style.header_nl_right;
    3617        5024 :     popt->nl_left = unicode_style.nl_left;
    3618        5024 :     popt->nl_right = unicode_style.nl_right;
    3619        5024 :     popt->wrap_left = unicode_style.wrap_left;
    3620        5024 :     popt->wrap_right = unicode_style.wrap_right;
    3621        5024 :     popt->wrap_right_border = unicode_style.wrap_right_border;
    3622        5024 : }
    3623             : 
    3624             : /*
    3625             :  * Compute the byte distance to the end of the string or *target_width
    3626             :  * display character positions, whichever comes first.  Update *target_width
    3627             :  * to be the number of display character positions actually filled.
    3628             :  */
    3629             : static int
    3630      632826 : strlen_max_width(unsigned char *str, int *target_width, int encoding)
    3631             : {
    3632      632826 :     unsigned char *start = str;
    3633      632826 :     unsigned char *end = str + strlen((char *) str);
    3634      632826 :     int         curr_width = 0;
    3635             : 
    3636     7519532 :     while (str < end)
    3637             :     {
    3638     6887726 :         int         char_width = PQdsplen((char *) str, encoding);
    3639             : 
    3640             :         /*
    3641             :          * If the display width of the new character causes the string to
    3642             :          * exceed its target width, skip it and return.  However, if this is
    3643             :          * the first character of the string (curr_width == 0), we have to
    3644             :          * accept it.
    3645             :          */
    3646     6887726 :         if (*target_width < curr_width + char_width && curr_width != 0)
    3647        1020 :             break;
    3648             : 
    3649     6886706 :         curr_width += char_width;
    3650             : 
    3651     6886706 :         str += PQmblen((char *) str, encoding);
    3652             :     }
    3653             : 
    3654      632826 :     *target_width = curr_width;
    3655             : 
    3656      632826 :     return str - start;
    3657             : }

Generated by: LCOV version 1.13