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