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