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