2000-01-19 00:30:24 +01:00
|
|
|
/*
|
|
|
|
* psql - the PostgreSQL interactive terminal
|
|
|
|
*
|
2014-01-07 22:05:30 +01:00
|
|
|
* Copyright (c) 2000-2014, PostgreSQL Global Development Group
|
2000-01-19 00:30:24 +01:00
|
|
|
*
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/bin/psql/print.c
|
2000-01-19 00:30:24 +01:00
|
|
|
*/
|
2008-03-27 04:57:34 +01:00
|
|
|
#include "postgres_fe.h"
|
|
|
|
|
2008-05-16 20:35:38 +02:00
|
|
|
#include <limits.h>
|
1999-11-04 22:56:02 +01:00
|
|
|
#include <math.h>
|
|
|
|
#include <signal.h>
|
2003-03-18 23:15:44 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#ifndef WIN32
|
|
|
|
#include <sys/ioctl.h> /* for ioctl() */
|
|
|
|
#endif
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2003-08-14 20:49:42 +02:00
|
|
|
#ifdef HAVE_TERMIOS_H
|
|
|
|
#include <termios.h>
|
|
|
|
#endif
|
|
|
|
|
2005-07-14 10:42:37 +02:00
|
|
|
#include <locale.h>
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
#include "catalog/pg_type.h"
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
#include "common.h"
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
#include "mbprint.h"
|
2008-05-13 00:59:58 +02:00
|
|
|
#include "print.h"
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
/*
|
|
|
|
* We define the cancel_pressed flag in this file, rather than common.c where
|
|
|
|
* it naturally belongs, because this file is also used by non-psql programs
|
|
|
|
* (see the bin/scripts/ directory). In those programs cancel_pressed will
|
|
|
|
* never become set and will have no effect.
|
|
|
|
*
|
|
|
|
* Note: print.c's general strategy for when to check cancel_pressed is to do
|
|
|
|
* so at completion of each row of output.
|
|
|
|
*/
|
|
|
|
volatile bool cancel_pressed = false;
|
|
|
|
|
2005-07-14 10:42:37 +02:00
|
|
|
static char *decimal_point;
|
|
|
|
static char *grouping;
|
|
|
|
static char *thousands_sep;
|
|
|
|
|
2012-06-10 21:20:04 +02:00
|
|
|
static char default_footer[100];
|
|
|
|
static printTableFooter default_footer_cell = {default_footer, NULL};
|
2012-05-01 22:03:45 +02:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
/* Line style control structures */
|
|
|
|
const printTextFormat pg_asciiformat =
|
|
|
|
{
|
|
|
|
"ascii",
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"", "|", "|", "|"}
|
2009-10-13 23:04:01 +02:00
|
|
|
},
|
2009-11-22 06:20:41 +01:00
|
|
|
"|",
|
|
|
|
"|",
|
|
|
|
"|",
|
|
|
|
" ",
|
|
|
|
"+",
|
|
|
|
" ",
|
|
|
|
"+",
|
|
|
|
".",
|
|
|
|
".",
|
|
|
|
true
|
|
|
|
};
|
|
|
|
|
|
|
|
const printTextFormat pg_asciiformat_old =
|
|
|
|
{
|
|
|
|
"old-ascii",
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"-", "+", "+", "+"},
|
|
|
|
{"", "|", "|", "|"}
|
2009-11-22 06:20:41 +01:00
|
|
|
},
|
2009-10-13 23:04:01 +02:00
|
|
|
":",
|
|
|
|
";",
|
2009-11-22 06:20:41 +01:00
|
|
|
" ",
|
|
|
|
"+",
|
|
|
|
" ",
|
|
|
|
" ",
|
|
|
|
" ",
|
|
|
|
" ",
|
|
|
|
" ",
|
|
|
|
false
|
2009-10-13 23:04:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const printTextFormat pg_utf8format =
|
|
|
|
{
|
|
|
|
"unicode",
|
|
|
|
{
|
|
|
|
/* ─, ┌, ┬, ┐ */
|
2010-02-26 03:01:40 +01:00
|
|
|
{"\342\224\200", "\342\224\214", "\342\224\254", "\342\224\220"},
|
2009-10-13 23:04:01 +02:00
|
|
|
/* ─, ├, ┼, ┤ */
|
2010-02-26 03:01:40 +01:00
|
|
|
{"\342\224\200", "\342\224\234", "\342\224\274", "\342\224\244"},
|
2009-10-13 23:04:01 +02:00
|
|
|
/* ─, └, ┴, ┘ */
|
2010-02-26 03:01:40 +01:00
|
|
|
{"\342\224\200", "\342\224\224", "\342\224\264", "\342\224\230"},
|
2009-10-13 23:04:01 +02:00
|
|
|
/* N/A, │, │, │ */
|
2010-02-26 03:01:40 +01:00
|
|
|
{"", "\342\224\202", "\342\224\202", "\342\224\202"}
|
2009-10-13 23:04:01 +02:00
|
|
|
},
|
2009-11-22 06:20:41 +01:00
|
|
|
/* │ */
|
|
|
|
"\342\224\202",
|
|
|
|
/* │ */
|
|
|
|
"\342\224\202",
|
|
|
|
/* │ */
|
|
|
|
"\342\224\202",
|
|
|
|
" ",
|
|
|
|
/* ↵ */
|
|
|
|
"\342\206\265",
|
|
|
|
" ",
|
|
|
|
/* ↵ */
|
|
|
|
"\342\206\265",
|
|
|
|
/* … */
|
|
|
|
"\342\200\246",
|
|
|
|
/* … */
|
|
|
|
"\342\200\246",
|
|
|
|
true
|
2009-10-13 23:04:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
/* Local functions */
|
2009-06-11 16:49:15 +02:00
|
|
|
static int strlen_max_width(unsigned char *str, int *target_width, int encoding);
|
2011-11-12 16:03:10 +01:00
|
|
|
static void IsPagerNeeded(const printTableContent *cont, const int extra_lines, bool expanded,
|
2009-06-11 16:49:15 +02:00
|
|
|
FILE **fout, bool *is_pager);
|
2008-05-10 05:31:58 +02:00
|
|
|
|
2011-11-12 16:03:10 +01:00
|
|
|
static void print_aligned_vertical(const printTableContent *cont, FILE *fout);
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2005-07-10 05:46:13 +02:00
|
|
|
static int
|
2005-07-14 23:12:41 +02:00
|
|
|
integer_digits(const char *my_str)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2005-10-15 04:49:52 +02:00
|
|
|
int frac_len;
|
2005-07-10 05:46:13 +02:00
|
|
|
|
|
|
|
if (my_str[0] == '-')
|
|
|
|
my_str++;
|
2005-10-15 04:49:52 +02:00
|
|
|
|
2005-07-14 23:12:41 +02:00
|
|
|
frac_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;
|
|
|
|
|
|
|
|
return strlen(my_str) - frac_len;
|
|
|
|
}
|
|
|
|
|
2005-07-18 22:57:53 +02:00
|
|
|
/* Return additional length required for locale-aware numeric output */
|
2005-07-14 23:12:41 +02:00
|
|
|
static int
|
2005-07-18 22:57:53 +02:00
|
|
|
additional_numeric_locale_len(const char *my_str)
|
2005-07-14 23:12:41 +02:00
|
|
|
{
|
2005-10-15 04:49:52 +02:00
|
|
|
int int_len = integer_digits(my_str),
|
|
|
|
len = 0;
|
|
|
|
int groupdigits = atoi(grouping);
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2005-07-18 21:27:37 +02:00
|
|
|
if (int_len > 0)
|
2005-07-18 20:58:45 +02:00
|
|
|
/* Don't count a leading separator */
|
2005-07-18 21:27:37 +02:00
|
|
|
len = (int_len / groupdigits - (int_len % groupdigits == 0)) *
|
2005-10-15 04:49:52 +02:00
|
|
|
strlen(thousands_sep);
|
2005-07-14 23:12:41 +02:00
|
|
|
|
2005-07-18 21:27:37 +02:00
|
|
|
if (strchr(my_str, '.') != NULL)
|
|
|
|
len += strlen(decimal_point) - strlen(".");
|
2005-10-15 04:49:52 +02:00
|
|
|
|
2005-07-18 21:27:37 +02:00
|
|
|
return len;
|
2005-07-10 05:46:13 +02:00
|
|
|
}
|
2005-07-14 08:46:17 +02:00
|
|
|
|
2005-07-10 05:46:13 +02:00
|
|
|
static int
|
2005-07-18 22:57:53 +02:00
|
|
|
strlen_with_numeric_locale(const char *my_str)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2005-07-18 22:57:53 +02:00
|
|
|
return strlen(my_str) + additional_numeric_locale_len(my_str);
|
2005-07-10 05:46:13 +02:00
|
|
|
}
|
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
/*
|
|
|
|
* Returns the appropriately formatted string in a new allocated block,
|
|
|
|
* caller must free
|
|
|
|
*/
|
2005-09-27 18:30:25 +02:00
|
|
|
static char *
|
|
|
|
format_numeric_locale(const char *my_str)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2005-10-15 04:49:52 +02:00
|
|
|
int i,
|
|
|
|
j,
|
|
|
|
int_len = integer_digits(my_str),
|
|
|
|
leading_digits;
|
|
|
|
int groupdigits = atoi(grouping);
|
|
|
|
int new_str_start = 0;
|
2012-10-02 21:35:10 +02:00
|
|
|
char *new_str = pg_malloc(strlen_with_numeric_locale(my_str) + 1);
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2005-07-14 23:12:41 +02:00
|
|
|
leading_digits = (int_len % groupdigits != 0) ?
|
2005-10-15 04:49:52 +02:00
|
|
|
int_len % groupdigits : groupdigits;
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2005-10-15 04:49:52 +02:00
|
|
|
if (my_str[0] == '-') /* skip over sign, affects grouping
|
|
|
|
* calculations */
|
2005-09-27 18:30:25 +02:00
|
|
|
{
|
|
|
|
new_str[0] = my_str[0];
|
|
|
|
my_str++;
|
|
|
|
new_str_start = 1;
|
|
|
|
}
|
|
|
|
|
2005-10-15 04:49:52 +02:00
|
|
|
for (i = 0, j = new_str_start;; i++, j++)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2005-07-14 23:12:41 +02:00
|
|
|
/* Hit decimal point? */
|
2005-07-10 05:46:13 +02:00
|
|
|
if (my_str[i] == '.')
|
|
|
|
{
|
2005-07-14 23:12:41 +02:00
|
|
|
strcpy(&new_str[j], decimal_point);
|
|
|
|
j += strlen(decimal_point);
|
|
|
|
/* add fractional part */
|
|
|
|
strcpy(&new_str[j], &my_str[i] + 1);
|
2005-07-10 05:46:13 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2005-07-14 23:12:41 +02:00
|
|
|
/* End of string? */
|
2005-07-10 05:46:13 +02:00
|
|
|
if (my_str[i] == '\0')
|
|
|
|
{
|
|
|
|
new_str[j] = '\0';
|
|
|
|
break;
|
|
|
|
}
|
2005-10-15 04:49:52 +02:00
|
|
|
|
2005-07-14 23:12:41 +02:00
|
|
|
/* Add separator? */
|
|
|
|
if (i != 0 && (i - leading_digits) % groupdigits == 0)
|
|
|
|
{
|
|
|
|
strcpy(&new_str[j], thousands_sep);
|
|
|
|
j += strlen(thousands_sep);
|
|
|
|
}
|
2005-07-10 05:46:13 +02:00
|
|
|
|
|
|
|
new_str[j] = my_str[i];
|
|
|
|
}
|
2005-10-15 04:49:52 +02:00
|
|
|
|
2005-09-27 18:30:25 +02:00
|
|
|
return new_str;
|
2005-07-10 05:46:13 +02:00
|
|
|
}
|
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
|
2010-05-08 18:39:53 +02:00
|
|
|
/*
|
|
|
|
* fputnbytes: print exactly N bytes to a file
|
|
|
|
*
|
2010-05-09 04:16:00 +02:00
|
|
|
* We avoid using %.*s here because it can misbehave if the data
|
|
|
|
* is not valid in what libc thinks is the prevailing encoding.
|
2010-05-08 18:39:53 +02:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
fputnbytes(FILE *f, const char *str, size_t n)
|
|
|
|
{
|
|
|
|
while (n-- > 0)
|
|
|
|
fputc(*str++, f);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-09 19:15:48 +01:00
|
|
|
static void
|
|
|
|
print_separator(struct separator sep, FILE *fout)
|
|
|
|
{
|
|
|
|
if (sep.separator_zero)
|
|
|
|
fputc('\000', fout);
|
|
|
|
else if (sep.separator)
|
|
|
|
fputs(sep.separator, fout);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
/*
|
|
|
|
* Return the list of explicitly-requested footers or, when applicable, the
|
2014-05-06 18:12:18 +02:00
|
|
|
* default "(xx rows)" footer. Always omit the default footer when given
|
2012-05-01 22:03:45 +02:00
|
|
|
* non-default footers, "\pset footer off", or a specific instruction to that
|
|
|
|
* effect from a calling backslash command. Vertical formats number each row,
|
|
|
|
* making the default footer redundant; they do not call this function.
|
|
|
|
*
|
|
|
|
* The return value may point to static storage; do not keep it across calls.
|
|
|
|
*/
|
|
|
|
static printTableFooter *
|
|
|
|
footers_with_default(const printTableContent *cont)
|
|
|
|
{
|
|
|
|
if (cont->footers == NULL && cont->opt->default_footer)
|
|
|
|
{
|
|
|
|
unsigned long total_records;
|
|
|
|
|
|
|
|
total_records = cont->opt->prior_records + cont->nrows;
|
|
|
|
snprintf(default_footer, sizeof(default_footer),
|
|
|
|
ngettext("(%lu row)", "(%lu rows)", total_records),
|
|
|
|
total_records);
|
|
|
|
|
|
|
|
return &default_footer_cell;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return cont->footers;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-11-04 22:56:02 +01:00
|
|
|
/*************************/
|
1999-11-05 00:14:30 +01:00
|
|
|
/* Unaligned text */
|
1999-11-04 22:56:02 +01:00
|
|
|
/*************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_unaligned_text(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
2000-04-12 19:17:23 +02:00
|
|
|
bool need_recordsep = false;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2012-02-09 19:15:48 +01:00
|
|
|
{
|
|
|
|
fputs(cont->title, fout);
|
|
|
|
print_separator(cont->opt->recordSep, fout);
|
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
/* print headers */
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
for (ptr = cont->headers; *ptr; ptr++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
if (ptr != cont->headers)
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->fieldSep, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(*ptr, fout);
|
|
|
|
}
|
|
|
|
need_recordsep = true;
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
else
|
|
|
|
/* assume continuing printout */
|
2000-04-12 19:17:23 +02:00
|
|
|
need_recordsep = true;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
|
|
|
/* print cells */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
if (need_recordsep)
|
|
|
|
{
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2000-04-12 19:17:23 +02:00
|
|
|
need_recordsep = false;
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
2010-03-01 21:55:45 +01:00
|
|
|
fputs(*ptr, fout);
|
2005-10-15 04:49:52 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns)
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->fieldSep, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2000-04-12 19:17:23 +02:00
|
|
|
need_recordsep = true;
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
|
|
|
if (!opt_tuples_only && footers != NULL && !cancel_pressed)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
if (need_recordsep)
|
|
|
|
{
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = false;
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
fputs(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = true;
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
2012-06-10 21:20:04 +02:00
|
|
|
|
2012-02-09 19:15:48 +01:00
|
|
|
/*
|
|
|
|
* The last record is terminated by a newline, independent of the set
|
|
|
|
* record separator. But when the record separator is a zero byte, we
|
|
|
|
* use that (compatible with find -print0 and xargs).
|
|
|
|
*/
|
2006-08-30 00:25:08 +02:00
|
|
|
if (need_recordsep)
|
2012-02-09 19:15:48 +01:00
|
|
|
{
|
|
|
|
if (cont->opt->recordSep.separator_zero)
|
|
|
|
print_separator(cont->opt->recordSep, fout);
|
|
|
|
else
|
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_unaligned_vertical(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
2006-08-30 00:25:08 +02:00
|
|
|
bool need_recordsep = false;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
fputs(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = true;
|
|
|
|
}
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
else
|
|
|
|
/* assume continuing printout */
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = true;
|
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
if (need_recordsep)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* record separator is 2 occurrences of recordsep in this mode */
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
need_recordsep = false;
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
fputs(cont->headers[i % cont->ncolumns], fout);
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->fieldSep, fout);
|
2010-03-01 21:55:45 +01:00
|
|
|
fputs(*ptr, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns)
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
else
|
|
|
|
need_recordsep = true;
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (f = cont->footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-02-09 19:15:48 +01:00
|
|
|
print_separator(cont->opt->recordSep, fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
fputs(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
2000-01-19 00:30:24 +01:00
|
|
|
|
2012-02-09 19:15:48 +01:00
|
|
|
/* see above in print_unaligned_text() */
|
2013-02-09 06:05:27 +01:00
|
|
|
if (need_recordsep)
|
|
|
|
{
|
|
|
|
if (cont->opt->recordSep.separator_zero)
|
|
|
|
print_separator(cont->opt->recordSep, fout);
|
|
|
|
else
|
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/********************/
|
1999-11-05 00:14:30 +01:00
|
|
|
/* Aligned text */
|
1999-11-04 22:56:02 +01:00
|
|
|
/********************/
|
|
|
|
|
|
|
|
|
|
|
|
/* draw "line" */
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
_print_horizontal_line(const unsigned int ncolumns, const unsigned int *widths,
|
2009-10-13 23:04:01 +02:00
|
|
|
unsigned short border, printTextRule pos,
|
|
|
|
const printTextFormat *format,
|
|
|
|
FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2009-10-13 23:04:01 +02:00
|
|
|
const printTextLineFormat *lformat = &format->lrule[pos];
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i,
|
|
|
|
j;
|
|
|
|
|
|
|
|
if (border == 1)
|
2009-10-13 23:04:01 +02:00
|
|
|
fputs(lformat->hrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else if (border == 2)
|
2009-10-13 23:04:01 +02:00
|
|
|
fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < ncolumns; i++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
for (j = 0; j < widths[i]; j++)
|
2009-10-13 23:04:01 +02:00
|
|
|
fputs(lformat->hrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i < ncolumns - 1)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
if (border == 0)
|
|
|
|
fputc(' ', fout);
|
|
|
|
else
|
2009-10-13 23:04:01 +02:00
|
|
|
fprintf(fout, "%s%s%s", lformat->hrule,
|
|
|
|
lformat->midvrule, lformat->hrule);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (border == 2)
|
2009-10-13 23:04:01 +02:00
|
|
|
fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
|
1999-11-05 00:14:30 +01:00
|
|
|
else if (border == 1)
|
2009-10-13 23:04:01 +02:00
|
|
|
fputs(lformat->hrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
|
|
|
fputc('\n', fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/*
|
2008-05-10 05:31:58 +02:00
|
|
|
* Print pretty boxes around cells.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
1999-11-04 22:56:02 +01:00
|
|
|
static void
|
2008-05-16 18:59:05 +02:00
|
|
|
print_aligned_text(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
int encoding = cont->opt->encoding;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
2009-10-13 23:04:01 +02:00
|
|
|
const printTextFormat *format = get_line_style(cont->opt);
|
|
|
|
const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2009-06-11 16:49:15 +02:00
|
|
|
unsigned int col_count = 0,
|
|
|
|
cell_count = 0;
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
unsigned int i,
|
|
|
|
j;
|
|
|
|
|
|
|
|
unsigned int *width_header,
|
|
|
|
*max_width,
|
|
|
|
*width_wrap,
|
|
|
|
*width_average;
|
2009-06-11 16:49:15 +02:00
|
|
|
unsigned int *max_nl_lines, /* value split by newlines */
|
|
|
|
*curr_nl_line,
|
|
|
|
*max_bytes;
|
2006-02-10 01:39:04 +01:00
|
|
|
unsigned char **format_buf;
|
2008-05-08 19:04:26 +02:00
|
|
|
unsigned int width_total;
|
|
|
|
unsigned int total_header_width;
|
2008-05-16 18:59:05 +02:00
|
|
|
unsigned int extra_row_output_lines = 0;
|
|
|
|
unsigned int extra_output_lines = 0;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2009-06-11 16:49:15 +02:00
|
|
|
const char *const * ptr;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
struct lineptr **col_lineptrs; /* pointers to line pointer per column */
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
bool *header_done; /* Have all header lines been output? */
|
|
|
|
int *bytes_output; /* Bytes output for column value */
|
2009-11-22 06:20:41 +01:00
|
|
|
printTextLineWrap *wrap; /* Wrap status for each column */
|
2009-06-11 16:49:15 +02:00
|
|
|
int output_columns = 0; /* Width of interactive console */
|
2008-05-16 18:59:05 +02:00
|
|
|
bool is_pager = false;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->ncolumns > 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
col_count = cont->ncolumns;
|
2012-10-02 21:35:10 +02:00
|
|
|
width_header = pg_malloc0(col_count * sizeof(*width_header));
|
|
|
|
width_average = pg_malloc0(col_count * sizeof(*width_average));
|
|
|
|
max_width = pg_malloc0(col_count * sizeof(*max_width));
|
|
|
|
width_wrap = pg_malloc0(col_count * sizeof(*width_wrap));
|
|
|
|
max_nl_lines = pg_malloc0(col_count * sizeof(*max_nl_lines));
|
|
|
|
curr_nl_line = pg_malloc0(col_count * sizeof(*curr_nl_line));
|
|
|
|
col_lineptrs = pg_malloc0(col_count * sizeof(*col_lineptrs));
|
|
|
|
max_bytes = pg_malloc0(col_count * sizeof(*max_bytes));
|
|
|
|
format_buf = pg_malloc0(col_count * sizeof(*format_buf));
|
|
|
|
header_done = pg_malloc0(col_count * sizeof(*header_done));
|
|
|
|
bytes_output = pg_malloc0(col_count * sizeof(*bytes_output));
|
|
|
|
wrap = pg_malloc0(col_count * sizeof(*wrap));
|
2002-11-01 16:12:19 +01:00
|
|
|
}
|
|
|
|
else
|
2001-10-25 07:50:21 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
width_header = NULL;
|
|
|
|
width_average = NULL;
|
|
|
|
max_width = NULL;
|
|
|
|
width_wrap = NULL;
|
|
|
|
max_nl_lines = NULL;
|
|
|
|
curr_nl_line = NULL;
|
2006-02-10 01:39:04 +01:00
|
|
|
col_lineptrs = NULL;
|
2008-05-08 19:04:26 +02:00
|
|
|
max_bytes = NULL;
|
2006-02-10 01:39:04 +01:00
|
|
|
format_buf = NULL;
|
2008-05-08 19:04:26 +02:00
|
|
|
header_done = NULL;
|
|
|
|
bytes_output = NULL;
|
2009-11-22 06:20:41 +01:00
|
|
|
wrap = NULL;
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* scan all column headers, find maximum width and max max_nl_lines */
|
2001-10-25 07:50:21 +02:00
|
|
|
for (i = 0; i < col_count; i++)
|
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int width,
|
|
|
|
nl_lines,
|
|
|
|
bytes_required;
|
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
|
2008-05-13 00:59:58 +02:00
|
|
|
encoding, &width, &nl_lines, &bytes_required);
|
2008-05-08 19:04:26 +02:00
|
|
|
if (width > max_width[i])
|
|
|
|
max_width[i] = width;
|
|
|
|
if (nl_lines > max_nl_lines[i])
|
|
|
|
max_nl_lines[i] = nl_lines;
|
|
|
|
if (bytes_required > max_bytes[i])
|
|
|
|
max_bytes[i] = bytes_required;
|
2008-05-16 18:59:05 +02:00
|
|
|
if (nl_lines > extra_row_output_lines)
|
|
|
|
extra_row_output_lines = nl_lines;
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
width_header[i] = width;
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
}
|
2008-05-16 18:59:05 +02:00
|
|
|
/* Add height of tallest header column */
|
|
|
|
extra_output_lines += extra_row_output_lines;
|
|
|
|
extra_row_output_lines = 0;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* scan all cells, find maximum width, compute cell_count */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; ptr++, i++, cell_count++)
|
2001-10-25 07:50:21 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int width,
|
|
|
|
nl_lines,
|
|
|
|
bytes_required;
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
|
2008-05-10 05:31:58 +02:00
|
|
|
&width, &nl_lines, &bytes_required);
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
if (width > max_width[i % col_count])
|
|
|
|
max_width[i % col_count] = width;
|
|
|
|
if (nl_lines > max_nl_lines[i % col_count])
|
|
|
|
max_nl_lines[i % col_count] = nl_lines;
|
|
|
|
if (bytes_required > max_bytes[i % col_count])
|
|
|
|
max_bytes[i % col_count] = bytes_required;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
width_average[i % col_count] += width;
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
}
|
2002-11-01 16:12:19 +01:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* If we have rows, compute average */
|
|
|
|
if (col_count != 0 && cell_count != 0)
|
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
int rows = cell_count / col_count;
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
for (i = 0; i < col_count; i++)
|
2008-05-17 19:52:14 +02:00
|
|
|
width_average[i] /= rows;
|
2008-05-08 19:04:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* adjust the total display width based on border style */
|
1999-11-05 00:14:30 +01:00
|
|
|
if (opt_border == 0)
|
2009-11-22 06:20:41 +01:00
|
|
|
width_total = col_count;
|
1999-11-05 00:14:30 +01:00
|
|
|
else if (opt_border == 1)
|
2008-05-08 19:04:26 +02:00
|
|
|
width_total = col_count * 3 - 1;
|
1999-11-04 22:56:02 +01:00
|
|
|
else
|
2008-05-08 19:04:26 +02:00
|
|
|
width_total = col_count * 3 + 1;
|
|
|
|
total_header_width = width_total;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
|
|
|
for (i = 0; i < col_count; i++)
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
|
|
|
width_total += max_width[i];
|
|
|
|
total_header_width += width_header[i];
|
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-10-04 02:30:14 +02:00
|
|
|
/*
|
2008-05-08 19:04:26 +02:00
|
|
|
* At this point: max_width[] contains the max width of each column,
|
|
|
|
* max_nl_lines[] contains the max number of lines in each column,
|
2009-06-11 16:49:15 +02:00
|
|
|
* max_bytes[] contains the maximum storage space for formatting strings,
|
|
|
|
* width_total contains the giant width sum. Now we allocate some memory
|
|
|
|
* for line pointers.
|
2006-02-10 01:39:04 +01:00
|
|
|
*/
|
2008-05-08 19:04:26 +02:00
|
|
|
for (i = 0; i < col_count; i++)
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/* Add entry for ptr == NULL array termination */
|
2012-10-02 21:35:10 +02:00
|
|
|
col_lineptrs[i] = pg_malloc0((max_nl_lines[i] + 1) *
|
|
|
|
sizeof(**col_lineptrs));
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
format_buf[i] = pg_malloc(max_bytes[i] + 1);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
col_lineptrs[i]->ptr = format_buf[i];
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* Default word wrap to the full width, i.e. no word wrap */
|
|
|
|
for (i = 0; i < col_count; i++)
|
|
|
|
width_wrap[i] = max_width[i];
|
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
/*
|
|
|
|
* Choose target output width: \pset columns, or $COLUMNS, or ioctl
|
|
|
|
*/
|
|
|
|
if (cont->opt->columns > 0)
|
|
|
|
output_columns = cont->opt->columns;
|
|
|
|
else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
2008-05-16 18:59:05 +02:00
|
|
|
if (cont->opt->env_columns > 0)
|
|
|
|
output_columns = cont->opt->env_columns;
|
2008-05-08 19:04:26 +02:00
|
|
|
#ifdef TIOCGWINSZ
|
2008-05-16 18:59:05 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
struct winsize screen_size;
|
|
|
|
|
|
|
|
if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
|
|
|
|
output_columns = screen_size.ws_col;
|
2008-05-08 19:04:26 +02:00
|
|
|
}
|
2008-05-16 18:59:05 +02:00
|
|
|
#endif
|
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
if (cont->opt->format == PRINT_WRAPPED)
|
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/*
|
2008-05-10 05:31:58 +02:00
|
|
|
* Optional optimized word wrap. Shrink columns with a high max/avg
|
|
|
|
* ratio. Slighly bias against wider columns. (Increases chance a
|
|
|
|
* narrow column will fit in its cell.) If available columns is
|
2014-05-06 18:12:18 +02:00
|
|
|
* positive... and greater than the width of the unshrinkable column
|
2008-05-10 05:31:58 +02:00
|
|
|
* headers
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
if (output_columns > 0 && output_columns >= total_header_width)
|
|
|
|
{
|
|
|
|
/* While there is still excess width... */
|
|
|
|
while (width_total > output_columns)
|
|
|
|
{
|
|
|
|
double max_ratio = 0;
|
|
|
|
int worst_col = -1;
|
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* Find column that has the highest ratio of its maximum width
|
|
|
|
* compared to its average width. This tells us which column
|
|
|
|
* will produce the fewest wrapped values if shortened.
|
|
|
|
* width_wrap starts as equal to max_width.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
for (i = 0; i < col_count; i++)
|
|
|
|
{
|
|
|
|
if (width_average[i] && width_wrap[i] > width_header[i])
|
|
|
|
{
|
2008-05-10 05:31:58 +02:00
|
|
|
/* Penalize wide columns by 1% of their width */
|
2009-06-11 16:49:15 +02:00
|
|
|
double ratio;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
ratio = (double) width_wrap[i] / width_average[i] +
|
|
|
|
max_width[i] * 0.01;
|
2008-05-08 19:04:26 +02:00
|
|
|
if (ratio > max_ratio)
|
|
|
|
{
|
|
|
|
max_ratio = ratio;
|
|
|
|
worst_col = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Exit loop if we can't squeeze any more. */
|
|
|
|
if (worst_col == -1)
|
|
|
|
break;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* Decrease width of target column by one. */
|
|
|
|
width_wrap[worst_col]--;
|
|
|
|
width_total--;
|
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2011-11-12 16:03:10 +01:00
|
|
|
/*
|
|
|
|
* If in expanded auto mode, we have now calculated the expected width, so
|
|
|
|
* we can now escape to vertical mode if necessary.
|
|
|
|
*/
|
|
|
|
if (cont->opt->expanded == 2 && output_columns > 0 &&
|
|
|
|
(output_columns < total_header_width || output_columns < width_total))
|
|
|
|
{
|
|
|
|
print_aligned_vertical(cont, fout);
|
2012-03-07 22:52:15 +01:00
|
|
|
goto cleanup;
|
2011-11-12 16:03:10 +01:00
|
|
|
}
|
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
/* If we wrapped beyond the display width, use the pager */
|
2008-05-17 23:40:44 +02:00
|
|
|
if (!is_pager && fout == stdout && output_columns > 0 &&
|
2008-05-16 18:59:05 +02:00
|
|
|
(output_columns < total_header_width || output_columns < width_total))
|
|
|
|
{
|
|
|
|
fout = PageOutput(INT_MAX, cont->opt->pager); /* force pager */
|
|
|
|
is_pager = true;
|
|
|
|
}
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
/* Check if newlines or our wrapping now need the pager */
|
|
|
|
if (!is_pager)
|
|
|
|
{
|
|
|
|
/* scan all cells, find maximum width, compute cell_count */
|
2008-05-17 19:52:14 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; ptr++, cell_count++)
|
2008-05-16 18:59:05 +02:00
|
|
|
{
|
|
|
|
int width,
|
|
|
|
nl_lines,
|
|
|
|
bytes_required;
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
|
2008-05-16 18:59:05 +02:00
|
|
|
&width, &nl_lines, &bytes_required);
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* A row can have both wrapping and newlines that cause it to
|
|
|
|
* display across multiple lines. We check for both cases below.
|
2008-05-16 18:59:05 +02:00
|
|
|
*/
|
2008-05-17 19:52:14 +02:00
|
|
|
if (width > 0 && width_wrap[i])
|
|
|
|
{
|
|
|
|
unsigned int extra_lines;
|
|
|
|
|
2009-06-11 16:49:15 +02:00
|
|
|
extra_lines = (width - 1) / width_wrap[i] + nl_lines;
|
2008-05-17 19:52:14 +02:00
|
|
|
if (extra_lines > extra_row_output_lines)
|
|
|
|
extra_row_output_lines = extra_lines;
|
|
|
|
}
|
2008-05-16 18:59:05 +02:00
|
|
|
|
2008-05-17 19:52:14 +02:00
|
|
|
/* i is the current column number: increment with wrap */
|
|
|
|
if (++i >= col_count)
|
2008-05-16 18:59:05 +02:00
|
|
|
{
|
2008-05-17 19:52:14 +02:00
|
|
|
i = 0;
|
|
|
|
/* At last column of each row, add tallest column height */
|
2008-05-16 18:59:05 +02:00
|
|
|
extra_output_lines += extra_row_output_lines;
|
|
|
|
extra_row_output_lines = 0;
|
|
|
|
}
|
|
|
|
}
|
2011-11-12 16:03:10 +01:00
|
|
|
IsPagerNeeded(cont, extra_output_lines, false, &fout, &is_pager);
|
2008-05-16 18:59:05 +02:00
|
|
|
}
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* time to output */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->title && !opt_tuples_only)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
int width,
|
|
|
|
height;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) cont->title, strlen(cont->title),
|
2008-05-13 00:59:58 +02:00
|
|
|
encoding, &width, &height, NULL);
|
2008-05-08 19:04:26 +02:00
|
|
|
if (width >= width_total)
|
2008-05-13 00:59:58 +02:00
|
|
|
/* Aligned */
|
|
|
|
fprintf(fout, "%s\n", cont->title);
|
2006-08-30 00:25:08 +02:00
|
|
|
else
|
2008-05-13 00:59:58 +02:00
|
|
|
/* Centered */
|
|
|
|
fprintf(fout, "%-*s%s\n", (width_total - width) / 2, "",
|
|
|
|
cont->title);
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print headers */
|
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int more_col_wrapping;
|
|
|
|
int curr_nl_line;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
if (opt_border == 2)
|
2009-10-13 23:04:01 +02:00
|
|
|
_print_horizontal_line(col_count, width_wrap, opt_border,
|
|
|
|
PRINT_RULE_TOP, format, fout);
|
2001-10-25 07:50:21 +02:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
for (i = 0; i < col_count; i++)
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcsformat((const unsigned char *) cont->headers[i],
|
2008-05-13 00:59:58 +02:00
|
|
|
strlen(cont->headers[i]), encoding,
|
|
|
|
col_lineptrs[i], max_nl_lines[i]);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
more_col_wrapping = col_count;
|
|
|
|
curr_nl_line = 0;
|
|
|
|
memset(header_done, false, col_count * sizeof(bool));
|
|
|
|
while (more_col_wrapping)
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border == 2)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->leftvrule, fout);
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont->ncolumns; i++)
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
struct lineptr *this_line = col_lineptrs[i] + curr_nl_line;
|
2009-10-13 23:04:01 +02:00
|
|
|
unsigned int nbspace;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2009-11-22 06:20:41 +01:00
|
|
|
if (opt_border != 0 ||
|
2010-11-15 03:03:48 +01:00
|
|
|
(!format->wrap_right_border && i > 0))
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(curr_nl_line ? format->header_nl_left : " ",
|
|
|
|
fout);
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
if (!header_done[i])
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
nbspace = width_wrap[i] - this_line->width;
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
/* centered */
|
|
|
|
fprintf(fout, "%-*s%s%-*s",
|
|
|
|
nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
if (!(this_line + 1)->ptr)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
more_col_wrapping--;
|
|
|
|
header_done[i] = 1;
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
|
|
|
else
|
2008-05-08 19:04:26 +02:00
|
|
|
fprintf(fout, "%*s", width_wrap[i], "");
|
2009-11-22 06:20:41 +01:00
|
|
|
|
2010-11-15 03:03:48 +01:00
|
|
|
if (opt_border != 0 || format->wrap_right_border)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(!header_done[i] ? format->header_nl_right : " ",
|
|
|
|
fout);
|
|
|
|
|
|
|
|
if (opt_border != 0 && i < col_count - 1)
|
|
|
|
fputs(dformat->midvrule, fout);
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
curr_nl_line++;
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
if (opt_border == 2)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->rightvrule, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
_print_horizontal_line(col_count, width_wrap, opt_border,
|
|
|
|
PRINT_RULE_MIDDLE, format, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* print cells, one loop per row */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i += col_count, ptr += col_count)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
bool more_lines;
|
2006-06-14 18:49:03 +02:00
|
|
|
|
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/*
|
2010-03-01 21:55:45 +01:00
|
|
|
* Format each cell.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
2006-02-10 01:39:04 +01:00
|
|
|
for (j = 0; j < col_count; j++)
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcsformat((const unsigned char *) ptr[j], strlen(ptr[j]), encoding,
|
2008-05-10 05:31:58 +02:00
|
|
|
col_lineptrs[j], max_nl_lines[j]);
|
2008-05-08 19:04:26 +02:00
|
|
|
curr_nl_line[j] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(bytes_output, 0, col_count * sizeof(int));
|
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* Each time through this loop, one display line is output. It can
|
|
|
|
* either be a full value or a partial value if embedded newlines
|
|
|
|
* exist or if 'format=wrapping' mode is enabled.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
do
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
more_lines = false;
|
|
|
|
|
|
|
|
/* left border */
|
1999-11-05 00:14:30 +01:00
|
|
|
if (opt_border == 2)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->leftvrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/* for each column */
|
2006-02-10 01:39:04 +01:00
|
|
|
for (j = 0; j < col_count; j++)
|
2005-10-15 04:49:52 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/* We have a valid array element, so index it */
|
|
|
|
struct lineptr *this_line = &col_lineptrs[j][curr_nl_line[j]];
|
2009-06-11 16:49:15 +02:00
|
|
|
int bytes_to_output;
|
|
|
|
int chars_to_output = width_wrap[j];
|
|
|
|
bool finalspaces = (opt_border == 2 || j < col_count - 1);
|
2006-02-12 03:56:21 +01:00
|
|
|
|
2009-11-22 06:20:41 +01:00
|
|
|
/* Print left-hand wrap or newline mark */
|
|
|
|
if (opt_border != 0)
|
|
|
|
{
|
|
|
|
if (wrap[j] == PRINT_LINE_WRAP_WRAP)
|
|
|
|
fputs(format->wrap_left, fout);
|
|
|
|
else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE)
|
|
|
|
fputs(format->nl_left, fout);
|
|
|
|
else
|
|
|
|
fputc(' ', fout);
|
|
|
|
}
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
if (!this_line->ptr)
|
2008-05-10 05:31:58 +02:00
|
|
|
{
|
|
|
|
/* Past newline lines so just pad for other columns */
|
|
|
|
if (finalspaces)
|
|
|
|
fprintf(fout, "%*s", chars_to_output, "");
|
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
else
|
|
|
|
{
|
2008-05-10 05:31:58 +02:00
|
|
|
/* Get strlen() of the characters up to width_wrap */
|
|
|
|
bytes_to_output =
|
|
|
|
strlen_max_width(this_line->ptr + bytes_output[j],
|
|
|
|
&chars_to_output, encoding);
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* If we exceeded width_wrap, it means the display width
|
|
|
|
* of a single character was wider than our target width.
|
|
|
|
* In that case, we have to pretend we are only printing
|
|
|
|
* the target display width and make the best of it.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
if (chars_to_output > width_wrap[j])
|
|
|
|
chars_to_output = width_wrap[j];
|
|
|
|
|
2009-06-11 16:49:15 +02:00
|
|
|
if (cont->aligns[j] == 'r') /* Right aligned cell */
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/* spaces first */
|
|
|
|
fprintf(fout, "%*s", width_wrap[j] - chars_to_output, "");
|
2010-05-08 18:39:53 +02:00
|
|
|
fputnbytes(fout,
|
2010-07-06 21:19:02 +02:00
|
|
|
(char *) (this_line->ptr + bytes_output[j]),
|
2010-05-08 18:39:53 +02:00
|
|
|
bytes_to_output);
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
else /* Left aligned cell */
|
|
|
|
{
|
|
|
|
/* spaces second */
|
2010-05-08 18:39:53 +02:00
|
|
|
fputnbytes(fout,
|
2010-07-06 21:19:02 +02:00
|
|
|
(char *) (this_line->ptr + bytes_output[j]),
|
2010-05-08 18:39:53 +02:00
|
|
|
bytes_to_output);
|
2008-05-08 19:04:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bytes_output[j] += bytes_to_output;
|
|
|
|
|
|
|
|
/* Do we have more text to wrap? */
|
2008-05-10 05:31:58 +02:00
|
|
|
if (*(this_line->ptr + bytes_output[j]) != '\0')
|
2008-05-08 19:04:26 +02:00
|
|
|
more_lines = true;
|
2006-02-10 01:39:04 +01:00
|
|
|
else
|
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
/* Advance to next newline line */
|
|
|
|
curr_nl_line[j]++;
|
|
|
|
if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
|
|
|
|
more_lines = true;
|
|
|
|
bytes_output[j] = 0;
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2009-11-22 06:20:41 +01:00
|
|
|
/* Determine next line's wrap status for this column */
|
|
|
|
wrap[j] = PRINT_LINE_WRAP_NONE;
|
|
|
|
if (col_lineptrs[j][curr_nl_line[j]].ptr != NULL)
|
2006-02-10 01:39:04 +01:00
|
|
|
{
|
2009-11-22 06:20:41 +01:00
|
|
|
if (bytes_output[j] != 0)
|
|
|
|
wrap[j] = PRINT_LINE_WRAP_WRAP;
|
|
|
|
else if (curr_nl_line[j] != 0)
|
|
|
|
wrap[j] = PRINT_LINE_WRAP_NEWLINE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If left-aligned, pad out remaining space if needed (not
|
|
|
|
* last column, and/or wrap marks required).
|
|
|
|
*/
|
2010-02-26 03:01:40 +01:00
|
|
|
if (cont->aligns[j] != 'r') /* Left aligned cell */
|
2009-11-22 06:20:41 +01:00
|
|
|
{
|
|
|
|
if (finalspaces ||
|
|
|
|
wrap[j] == PRINT_LINE_WRAP_WRAP ||
|
2010-02-26 03:01:40 +01:00
|
|
|
wrap[j] == PRINT_LINE_WRAP_NEWLINE)
|
2009-11-22 06:20:41 +01:00
|
|
|
fprintf(fout, "%*s",
|
|
|
|
width_wrap[j] - chars_to_output, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Print right-hand wrap or newline mark */
|
|
|
|
if (wrap[j] == PRINT_LINE_WRAP_WRAP)
|
|
|
|
fputs(format->wrap_right, fout);
|
|
|
|
else if (wrap[j] == PRINT_LINE_WRAP_NEWLINE)
|
|
|
|
fputs(format->nl_right, fout);
|
|
|
|
else if (opt_border == 2 || j < col_count - 1)
|
|
|
|
fputc(' ', fout);
|
|
|
|
|
|
|
|
/* Print column divider, if not the last column */
|
|
|
|
if (opt_border != 0 && j < col_count - 1)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
if (wrap[j + 1] == PRINT_LINE_WRAP_WRAP)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(format->midvrule_wrap, fout);
|
2010-02-26 03:01:40 +01:00
|
|
|
else if (wrap[j + 1] == PRINT_LINE_WRAP_NEWLINE)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(format->midvrule_nl, fout);
|
2009-06-11 16:49:15 +02:00
|
|
|
else if (col_lineptrs[j + 1][curr_nl_line[j + 1]].ptr == NULL)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(format->midvrule_blank, fout);
|
2006-02-10 01:39:04 +01:00
|
|
|
else
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->midvrule, fout);
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
2005-09-27 18:30:25 +02:00
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
/* end-of-row border */
|
1999-11-05 00:14:30 +01:00
|
|
|
if (opt_border == 2)
|
2009-11-22 06:20:41 +01:00
|
|
|
fputs(dformat->rightvrule, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
fputc('\n', fout);
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
} while (more_lines);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border == 2 && !cancel_pressed)
|
2009-10-13 23:04:01 +02:00
|
|
|
_print_horizontal_line(col_count, width_wrap, opt_border,
|
|
|
|
PRINT_RULE_BOTTOM, format, fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2012-05-01 22:03:45 +02:00
|
|
|
if (footers && !opt_tuples_only && !cancel_pressed)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2008-05-13 00:59:58 +02:00
|
|
|
fprintf(fout, "%s\n", f->data);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2012-03-07 22:52:15 +01:00
|
|
|
cleanup:
|
1999-11-05 00:14:30 +01:00
|
|
|
/* clean up */
|
2010-01-30 19:59:51 +01:00
|
|
|
for (i = 0; i < col_count; i++)
|
|
|
|
{
|
|
|
|
free(col_lineptrs[i]);
|
|
|
|
free(format_buf[i]);
|
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
free(width_header);
|
|
|
|
free(width_average);
|
|
|
|
free(max_width);
|
|
|
|
free(width_wrap);
|
|
|
|
free(max_nl_lines);
|
|
|
|
free(curr_nl_line);
|
2006-02-10 01:39:04 +01:00
|
|
|
free(col_lineptrs);
|
2008-05-08 19:04:26 +02:00
|
|
|
free(max_bytes);
|
2010-01-30 19:59:51 +01:00
|
|
|
free(format_buf);
|
2008-05-08 19:04:26 +02:00
|
|
|
free(header_done);
|
|
|
|
free(bytes_output);
|
2010-01-30 19:59:51 +01:00
|
|
|
free(wrap);
|
2008-05-16 18:59:05 +02:00
|
|
|
|
|
|
|
if (is_pager)
|
|
|
|
ClosePager(fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
static void
|
|
|
|
print_aligned_vertical_line(const printTableContent *cont,
|
|
|
|
unsigned long record,
|
|
|
|
unsigned int hwidth,
|
|
|
|
unsigned int dwidth,
|
|
|
|
printTextRule pos,
|
|
|
|
FILE *fout)
|
|
|
|
{
|
|
|
|
const printTextFormat *format = get_line_style(cont->opt);
|
|
|
|
const printTextLineFormat *lformat = &format->lrule[pos];
|
2010-02-26 03:01:40 +01:00
|
|
|
unsigned short opt_border = cont->opt->border;
|
|
|
|
unsigned int i;
|
|
|
|
int reclen = 0;
|
2009-10-13 23:04:01 +02:00
|
|
|
|
|
|
|
if (opt_border == 2)
|
|
|
|
fprintf(fout, "%s%s", lformat->leftvrule, lformat->hrule);
|
|
|
|
else if (opt_border == 1)
|
|
|
|
fputs(lformat->hrule, fout);
|
|
|
|
|
|
|
|
if (record)
|
|
|
|
{
|
|
|
|
if (opt_border == 0)
|
|
|
|
reclen = fprintf(fout, "* Record %lu", record);
|
|
|
|
else
|
|
|
|
reclen = fprintf(fout, "[ RECORD %lu ]", record);
|
|
|
|
}
|
|
|
|
if (opt_border != 2)
|
|
|
|
reclen++;
|
|
|
|
if (reclen < 0)
|
|
|
|
reclen = 0;
|
|
|
|
for (i = reclen; i < hwidth; i++)
|
|
|
|
fputs(opt_border > 0 ? lformat->hrule : " ", fout);
|
|
|
|
reclen -= hwidth;
|
|
|
|
|
|
|
|
if (opt_border > 0)
|
|
|
|
{
|
|
|
|
if (reclen-- <= 0)
|
|
|
|
fputs(lformat->hrule, fout);
|
|
|
|
if (reclen-- <= 0)
|
|
|
|
fputs(lformat->midvrule, fout);
|
|
|
|
if (reclen-- <= 0)
|
|
|
|
fputs(lformat->hrule, fout);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (reclen-- <= 0)
|
|
|
|
fputc(' ', fout);
|
|
|
|
}
|
|
|
|
if (reclen < 0)
|
|
|
|
reclen = 0;
|
|
|
|
for (i = reclen; i < dwidth; i++)
|
|
|
|
fputs(opt_border > 0 ? lformat->hrule : " ", fout);
|
|
|
|
if (opt_border == 2)
|
|
|
|
fprintf(fout, "%s%s", lformat->hrule, lformat->rightvrule);
|
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
|
|
|
|
1999-11-04 22:56:02 +01:00
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_aligned_vertical(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
2009-10-13 23:04:01 +02:00
|
|
|
const printTextFormat *format = get_line_style(cont->opt);
|
|
|
|
const printTextLineFormat *dformat = &format->lrule[PRINT_RULE_DATA];
|
2008-05-13 00:59:58 +02:00
|
|
|
int encoding = cont->opt->encoding;
|
|
|
|
unsigned long record = cont->opt->prior_records + 1;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i,
|
|
|
|
hwidth = 0,
|
2006-02-10 01:39:04 +01:00
|
|
|
dwidth = 0,
|
|
|
|
hheight = 1,
|
|
|
|
dheight = 1,
|
|
|
|
hformatsize = 0,
|
|
|
|
dformatsize = 0;
|
2006-10-04 02:30:14 +02:00
|
|
|
struct lineptr *hlineptr,
|
|
|
|
*dlineptr;
|
2011-11-12 16:03:10 +01:00
|
|
|
bool is_pager = false;
|
2014-04-28 19:41:36 +02:00
|
|
|
int output_columns = 0; /* Width of interactive console */
|
2006-06-14 18:49:03 +02:00
|
|
|
|
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->cells[0] == NULL && cont->opt->start_table &&
|
|
|
|
cont->opt->stop_table)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2013-09-11 01:07:06 +02:00
|
|
|
if (!opt_tuples_only && cont->opt->default_footer)
|
2013-02-09 05:39:22 +01:00
|
|
|
fprintf(fout, _("(No rows)\n"));
|
2000-04-12 19:17:23 +02:00
|
|
|
return;
|
|
|
|
}
|
1999-12-10 04:59:30 +01:00
|
|
|
|
2011-11-12 16:03:10 +01:00
|
|
|
/*
|
|
|
|
* Deal with the pager here instead of in printTable(), because we could
|
|
|
|
* get here via print_aligned_text() in expanded auto mode, and so we have
|
|
|
|
* to recalcuate the pager requirement based on vertical output.
|
|
|
|
*/
|
|
|
|
IsPagerNeeded(cont, 0, true, &fout, &is_pager);
|
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
/* Find the maximum dimensions for the headers */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont->ncolumns; i++)
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int width,
|
|
|
|
height,
|
2006-10-04 02:30:14 +02:00
|
|
|
fs;
|
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) cont->headers[i], strlen(cont->headers[i]),
|
2008-05-13 00:59:58 +02:00
|
|
|
encoding, &width, &height, &fs);
|
2008-05-08 19:04:26 +02:00
|
|
|
if (width > hwidth)
|
|
|
|
hwidth = width;
|
2006-02-10 01:39:04 +01:00
|
|
|
if (height > hheight)
|
|
|
|
hheight = height;
|
|
|
|
if (fs > hformatsize)
|
|
|
|
hformatsize = fs;
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
}
|
2003-04-04 17:48:38 +02:00
|
|
|
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
/* find longest data cell */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; ptr++, i++)
|
2001-10-25 07:50:21 +02:00
|
|
|
{
|
2008-05-08 19:04:26 +02:00
|
|
|
int width,
|
|
|
|
height,
|
2006-10-04 02:30:14 +02:00
|
|
|
fs;
|
2005-07-10 05:46:13 +02:00
|
|
|
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcssize((const unsigned char *) *ptr, strlen(*ptr), encoding,
|
2008-05-10 05:31:58 +02:00
|
|
|
&width, &height, &fs);
|
2008-05-08 19:04:26 +02:00
|
|
|
if (width > dwidth)
|
|
|
|
dwidth = width;
|
2006-02-10 01:39:04 +01:00
|
|
|
if (height > dheight)
|
|
|
|
dheight = height;
|
|
|
|
if (fs > dformatsize)
|
|
|
|
dformatsize = fs;
|
Commit Patrice's patches except:
> - corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
> characters (characters with values >= 0x10000, which are encoded on
> four bytes).
Also, update mb/expected/unicode.out. This is necessary since the
patches affetc the result of queries using UTF-8.
---------------------------------------------------------------
Hi,
I should have sent the patch earlier, but got delayed by other stuff.
Anyway, here is the patch:
- most of the functionality is only activated when MULTIBYTE is
defined,
- check valid UTF-8 characters, client-side only yet, and only on
output, you still can send invalid UTF-8 to the server (so, it's
only partly compliant to Unicode 3.1, but that's better than
nothing).
- formats with the correct number of columns (that's why I made it in
the first place after all), but only for UNICODE. However, the code
allows to plug-in routines for other encodings, as Tatsuo did for
the other multibyte functions.
- corrects a bit the UTF-8 code from Tatsuo to allow Unicode 3.1
characters (characters with values >= 0x10000, which are encoded on
four bytes).
- doesn't depend on the locale capabilities of the glibc (useful for
remote telnet).
I would like somebody to check it closely, as it is my first patch to
pgsql. Also, I created dummy .orig files, so that the two files I
created are included, I hope that's the right way.
Now, a lot of functionality is NOT included here, but I will keep that
for 7.3 :) That includes all string checking on the server side (which
will have to be a bit more optimised ;) ), and the input checking on
the client side for UTF-8, though that should not be difficult. It's
just to send the strings through mbvalidate() before sending them to
the server. Strong checking on UTF-8 strings is mandatory to be
compliant with Unicode 3.1+ .
Do I have time to look for a patch to include iso-8859-15 for 7.2 ?
The euro is coming 1. january 2002 (before 7.3 !) and over 280
millions people in Europe will need the euro sign and only iso-8859-15
and iso-8859-16 have it (and unfortunately, I don't think all Unices
will switch to Unicode in the meantime)....
err... yes, I know that this is not every single person in Europe that
uses PostgreSql, so it's not exactly 280m, but it's just a matter of
time ! ;)
I'll come back (on pgsql-hackers) later to ask a few questions
regarding the full unicode support (normalisation, collation,
regexes,...) on the server side :)
Here is the patch !
Patrice.
--
Patrice HÉDÉ ------------------------------- patrice à islande org -----
-- Isn't it weird how scientists can imagine all the matter of the
universe exploding out of a dot smaller than the head of a pin, but they
can't come up with a more evocative name for it than "The Big Bang" ?
-- What would _you_ call the creation of the universe ?
-- "The HORRENDOUS SPACE KABLOOIE !" - Calvin and Hobbes
------------------------------------------ http://www.islande.org/ -----
2001-10-15 03:25:10 +02:00
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We now have all the information we need to setup the formatting
|
|
|
|
* structures
|
|
|
|
*/
|
2012-10-02 21:35:10 +02:00
|
|
|
dlineptr = pg_malloc((sizeof(*dlineptr)) * (dheight + 1));
|
|
|
|
hlineptr = pg_malloc((sizeof(*hlineptr)) * (hheight + 1));
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
dlineptr->ptr = pg_malloc(dformatsize);
|
|
|
|
hlineptr->ptr = pg_malloc(hformatsize);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
|
|
|
fprintf(fout, "%s\n", cont->title);
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
/*
|
|
|
|
* Choose target output width: \pset columns, or $COLUMNS, or ioctl
|
|
|
|
*/
|
|
|
|
if (cont->opt->columns > 0)
|
|
|
|
output_columns = cont->opt->columns;
|
|
|
|
else if ((fout == stdout && isatty(fileno(stdout))) || is_pager)
|
|
|
|
{
|
|
|
|
if (cont->opt->env_columns > 0)
|
|
|
|
output_columns = cont->opt->env_columns;
|
|
|
|
#ifdef TIOCGWINSZ
|
|
|
|
else
|
|
|
|
{
|
|
|
|
struct winsize screen_size;
|
|
|
|
|
|
|
|
if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) != -1)
|
|
|
|
output_columns = screen_size.ws_col;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cont->opt->format == PRINT_WRAPPED)
|
|
|
|
{
|
2014-05-06 18:12:18 +02:00
|
|
|
/*
|
|
|
|
* Calculate the available width to wrap the columns to after
|
2014-04-28 19:41:36 +02:00
|
|
|
* subtracting the maximum header width and separators. At a minimum
|
2014-05-06 18:12:18 +02:00
|
|
|
* enough to print "[ RECORD N ]"
|
|
|
|
*/
|
|
|
|
unsigned int width,
|
|
|
|
swidth;
|
2014-04-28 19:41:36 +02:00
|
|
|
|
|
|
|
if (opt_border == 0)
|
2014-05-06 18:12:18 +02:00
|
|
|
swidth = 1; /* "header data" */
|
2014-04-28 19:41:36 +02:00
|
|
|
else if (opt_border == 1)
|
2014-05-06 18:12:18 +02:00
|
|
|
swidth = 3; /* "header | data" */
|
2014-04-30 17:15:15 +02:00
|
|
|
else
|
2014-05-06 18:12:18 +02:00
|
|
|
swidth = 7; /* "| header | data |" */
|
2014-04-28 19:41:36 +02:00
|
|
|
|
|
|
|
/* Wrap to maximum width */
|
|
|
|
width = dwidth + swidth + hwidth;
|
|
|
|
if ((output_columns > 0) && (width > output_columns))
|
|
|
|
{
|
|
|
|
dwidth = output_columns - hwidth - swidth;
|
|
|
|
width = output_columns;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Wrap to minimum width */
|
|
|
|
if (!opt_tuples_only)
|
|
|
|
{
|
2014-05-06 18:12:18 +02:00
|
|
|
int delta = 1 + log10(cont->nrows) - width;
|
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
if (opt_border == 0)
|
2014-05-06 18:12:18 +02:00
|
|
|
delta += 6; /* "* RECORD " */
|
2014-04-28 19:41:36 +02:00
|
|
|
else if (opt_border == 1)
|
2014-05-06 18:12:18 +02:00
|
|
|
delta += 10; /* "-[ RECORD ]" */
|
2014-04-30 17:15:15 +02:00
|
|
|
else
|
2014-05-06 18:12:18 +02:00
|
|
|
delta += 15; /* "+-[ RECORD ]-+" */
|
2014-04-28 19:41:36 +02:00
|
|
|
|
|
|
|
if (delta > 0)
|
|
|
|
dwidth += delta;
|
|
|
|
}
|
|
|
|
else if (dwidth < 3)
|
|
|
|
dwidth = 3;
|
|
|
|
}
|
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2009-10-13 23:04:01 +02:00
|
|
|
printTextRule pos;
|
2014-04-28 19:41:36 +02:00
|
|
|
int dline,
|
|
|
|
hline,
|
2006-10-04 02:30:14 +02:00
|
|
|
dcomplete,
|
2014-04-28 19:41:36 +02:00
|
|
|
hcomplete,
|
|
|
|
offset,
|
|
|
|
chars_to_output;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (i == 0)
|
2010-02-26 03:01:40 +01:00
|
|
|
pos = PRINT_RULE_TOP;
|
2009-10-13 23:04:01 +02:00
|
|
|
else
|
|
|
|
pos = PRINT_RULE_MIDDLE;
|
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
/* Print record header (e.g. "[ RECORD N ]") above each record */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2009-10-13 23:04:01 +02:00
|
|
|
print_aligned_vertical_line(cont, record++, hwidth, dwidth,
|
|
|
|
pos, fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
else if (i != 0 || !cont->opt->start_table || opt_border == 2)
|
2009-10-13 23:04:01 +02:00
|
|
|
print_aligned_vertical_line(cont, 0, hwidth, dwidth,
|
|
|
|
pos, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
* Includes tab completion. It's not magic, but it's very cool. At any
rate
it's better than what used to be there.
* Does proper SQL "host variable" substitution as pointed out by Andreas
Zeugwetter (thanks): select * from :foo; Also some changes in how ':'
and ';' are treated (escape with \ to send to backend). This does
_not_
affect the '::' cast operator, but perhaps others that contain : or ;
(but there are none right now).
* To show description with a <something> listing, append '?' to command
name, e.g., \df?. This seemed to be the convenient and logical
solution.
Or append a '+' to see more useless information, e.g., \df+.
* Fixed fflush()'ing bug pointed out by Jan during the regression test
discussion.
* Added LastOid variable. This ought to take care of TODO item "Add a
function to return the last inserted oid, for use in psql scripts"
(under CLIENTS)
E.g.,
insert into foo values(...);
insert into bar values(..., :LastOid);
\echo $LastOid
* \d command shows constraints, rules, and triggers defined on the table
(in addition to indices)
* Various fixes, optimizations, corrections
* Documentation update as well
Note: This now requires snprintf(), which, if necessary, is taken from
src/backend/port. This is certainly a little weird, but it should
suffice
until a source tree cleanup is done.
Enjoy.
--
Peter Eisentraut Sernanders väg 10:115
1999-11-26 05:24:17 +01:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
/* Format the header */
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcsformat((const unsigned char *) cont->headers[i % cont->ncolumns],
|
2008-05-13 00:59:58 +02:00
|
|
|
strlen(cont->headers[i % cont->ncolumns]),
|
2008-05-10 05:31:58 +02:00
|
|
|
encoding, hlineptr, hheight);
|
2006-02-10 01:39:04 +01:00
|
|
|
/* Format the data */
|
2011-09-11 20:54:32 +02:00
|
|
|
pg_wcsformat((const unsigned char *) *ptr, strlen(*ptr), encoding,
|
2008-05-10 05:31:58 +02:00
|
|
|
dlineptr, dheight);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2014-05-06 18:12:18 +02:00
|
|
|
/*
|
|
|
|
* Loop through header and data in parallel dealing with newlines and
|
|
|
|
* wrapped lines until they're both exhausted
|
|
|
|
*/
|
2014-04-28 19:41:36 +02:00
|
|
|
dline = hline = 0;
|
2006-02-10 01:39:04 +01:00
|
|
|
dcomplete = hcomplete = 0;
|
2014-04-28 19:41:36 +02:00
|
|
|
offset = 0;
|
|
|
|
chars_to_output = dlineptr[dline].width;
|
2006-02-10 01:39:04 +01:00
|
|
|
while (!dcomplete || !hcomplete)
|
2005-07-10 05:46:13 +02:00
|
|
|
{
|
2014-04-28 19:41:36 +02:00
|
|
|
/* Left border */
|
2006-02-10 01:39:04 +01:00
|
|
|
if (opt_border == 2)
|
2014-04-28 19:41:36 +02:00
|
|
|
fprintf(fout, "%s", dformat->leftvrule);
|
|
|
|
|
|
|
|
/* Header (never wrapped so just need to deal with newlines) */
|
2006-02-10 01:39:04 +01:00
|
|
|
if (!hcomplete)
|
|
|
|
{
|
2014-05-06 18:12:18 +02:00
|
|
|
int swidth,
|
|
|
|
twidth = hwidth + 1;
|
|
|
|
|
|
|
|
fputs(hline ? format->header_nl_left : " ", fout);
|
2014-04-29 13:43:03 +02:00
|
|
|
strlen_max_width(hlineptr[hline].ptr, &twidth,
|
2014-04-28 19:41:36 +02:00
|
|
|
encoding);
|
|
|
|
fprintf(fout, "%-s", hlineptr[hline].ptr);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
swidth = hwidth - twidth;
|
|
|
|
if (swidth > 0) /* spacer */
|
|
|
|
fprintf(fout, "%*s", swidth, " ");
|
|
|
|
|
|
|
|
if (hlineptr[hline + 1].ptr)
|
|
|
|
{
|
|
|
|
/* More lines after this one due to a newline */
|
|
|
|
fputs(format->header_nl_right, fout);
|
|
|
|
hline++;
|
2014-04-30 03:35:07 +02:00
|
|
|
}
|
|
|
|
else
|
2014-04-28 19:41:36 +02:00
|
|
|
{
|
|
|
|
/* This was the last line of the header */
|
|
|
|
fputs(" ", fout);
|
2006-02-10 01:39:04 +01:00
|
|
|
hcomplete = 1;
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
}
|
2005-07-10 05:46:13 +02:00
|
|
|
else
|
2014-04-28 19:41:36 +02:00
|
|
|
{
|
|
|
|
/* Header exhausted but more data for column */
|
|
|
|
fprintf(fout, "%*s", hwidth + 2, "");
|
|
|
|
}
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
/* Separator */
|
2006-02-10 01:39:04 +01:00
|
|
|
if (opt_border > 0)
|
2014-04-28 19:41:36 +02:00
|
|
|
{
|
|
|
|
if (offset)
|
|
|
|
fputs(format->midvrule_wrap, fout);
|
|
|
|
else if (!dline)
|
|
|
|
fputs(dformat->midvrule, fout);
|
|
|
|
else if (dline)
|
|
|
|
fputs(format->midvrule_nl, fout);
|
|
|
|
else
|
|
|
|
fputs(format->midvrule_blank, fout);
|
|
|
|
}
|
2006-02-10 01:39:04 +01:00
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
/* Data */
|
2006-02-10 01:39:04 +01:00
|
|
|
if (!dcomplete)
|
|
|
|
{
|
2014-05-06 18:12:18 +02:00
|
|
|
int target_width,
|
|
|
|
bytes_to_output,
|
|
|
|
swidth;
|
2014-04-28 19:41:36 +02:00
|
|
|
|
2014-05-06 18:12:18 +02:00
|
|
|
fputs(!dcomplete && !offset ? " " : format->wrap_left, fout);
|
2006-10-04 02:30:14 +02:00
|
|
|
|
2014-04-28 19:41:36 +02:00
|
|
|
target_width = dwidth;
|
|
|
|
bytes_to_output = strlen_max_width(dlineptr[dline].ptr + offset,
|
|
|
|
&target_width, encoding);
|
2014-05-06 18:12:18 +02:00
|
|
|
fputnbytes(fout, (char *) (dlineptr[dline].ptr + offset),
|
2014-04-28 19:41:36 +02:00
|
|
|
bytes_to_output);
|
|
|
|
|
|
|
|
chars_to_output -= target_width;
|
|
|
|
offset += bytes_to_output;
|
|
|
|
|
|
|
|
/* spacer */
|
|
|
|
swidth = dwidth - target_width;
|
|
|
|
if (swidth > 0)
|
|
|
|
fprintf(fout, "%*s", swidth, "");
|
|
|
|
|
|
|
|
if (chars_to_output)
|
|
|
|
{
|
|
|
|
/* continuing a wrapped column */
|
|
|
|
fputs(format->wrap_right, fout);
|
|
|
|
}
|
|
|
|
else if (dlineptr[dline + 1].ptr)
|
|
|
|
{
|
|
|
|
/* reached a newline in the column */
|
|
|
|
fputs(format->nl_right, fout);
|
|
|
|
dline++;
|
|
|
|
offset = 0;
|
|
|
|
chars_to_output = dlineptr[dline].width;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* reached the end of the cell */
|
|
|
|
fputs(" ", fout);
|
2006-10-04 02:30:14 +02:00
|
|
|
dcomplete = 1;
|
2014-04-28 19:41:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (opt_border == 2)
|
|
|
|
fputs(dformat->rightvrule, fout);
|
|
|
|
|
|
|
|
fputs("\n", fout);
|
2006-10-04 02:30:14 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-05-06 18:12:18 +02:00
|
|
|
/*
|
|
|
|
* data exhausted (this can occur if header is longer than the
|
|
|
|
* data due to newlines in the header)
|
|
|
|
*/
|
2006-10-04 02:30:14 +02:00
|
|
|
if (opt_border < 2)
|
2014-04-28 19:41:36 +02:00
|
|
|
fputs("\n", fout);
|
2006-10-04 02:30:14 +02:00
|
|
|
else
|
2014-04-28 19:41:36 +02:00
|
|
|
fprintf(fout, "%*s %s\n", dwidth, "", dformat->rightvrule);
|
2006-10-04 02:30:14 +02:00
|
|
|
}
|
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
if (opt_border == 2 && !cancel_pressed)
|
2009-10-13 23:04:01 +02:00
|
|
|
print_aligned_vertical_line(cont, 0, hwidth, dwidth,
|
|
|
|
PRINT_RULE_BOTTOM, fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border < 2)
|
|
|
|
fputc('\n', fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (f = cont->footers; f; f = f->next)
|
|
|
|
fprintf(fout, "%s\n", f->data);
|
2006-08-30 00:25:08 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-02-10 01:39:04 +01:00
|
|
|
free(hlineptr->ptr);
|
|
|
|
free(dlineptr->ptr);
|
|
|
|
free(hlineptr);
|
|
|
|
free(dlineptr);
|
2011-11-12 16:03:10 +01:00
|
|
|
|
|
|
|
if (is_pager)
|
|
|
|
ClosePager(fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**********************/
|
|
|
|
/* HTML printing ******/
|
|
|
|
/**********************/
|
|
|
|
|
|
|
|
|
2003-06-12 09:52:51 +02:00
|
|
|
void
|
1999-11-05 00:14:30 +01:00
|
|
|
html_escaped_print(const char *in, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
const char *p;
|
2005-10-15 04:49:52 +02:00
|
|
|
bool leading_space = true;
|
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
for (p = in; *p; p++)
|
2005-06-15 00:15:57 +02:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case '&':
|
|
|
|
fputs("&", fout);
|
|
|
|
break;
|
|
|
|
case '<':
|
|
|
|
fputs("<", fout);
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
fputs(">", fout);
|
|
|
|
break;
|
|
|
|
case '\n':
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs("<br />\n", fout);
|
|
|
|
break;
|
|
|
|
case '"':
|
|
|
|
fputs(""", fout);
|
|
|
|
break;
|
2005-06-15 00:15:57 +02:00
|
|
|
case ' ':
|
|
|
|
/* protect leading space, for EXPLAIN output */
|
|
|
|
if (leading_space)
|
|
|
|
fputs(" ", fout);
|
|
|
|
else
|
|
|
|
fputs(" ", fout);
|
|
|
|
break;
|
1999-11-05 00:14:30 +01:00
|
|
|
default:
|
|
|
|
fputc(*p, fout);
|
|
|
|
}
|
2005-06-15 00:15:57 +02:00
|
|
|
if (*p != ' ')
|
|
|
|
leading_space = false;
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_html_text(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
|
|
|
const char *opt_table_attr = cont->opt->tableAttr;
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout, "<table border=\"%d\"", opt_border);
|
|
|
|
if (opt_table_attr)
|
|
|
|
fprintf(fout, " %s", opt_table_attr);
|
|
|
|
fputs(">\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(" <caption>", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("</caption>\n", fout);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* print headers */
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" <tr>\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (ptr = cont->headers; *ptr; ptr++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(" <th align=\"center\">", fout);
|
|
|
|
html_escaped_print(*ptr, fout);
|
|
|
|
fputs("</th>\n", fout);
|
|
|
|
}
|
|
|
|
fputs(" </tr>\n", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* print cells */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
2006-06-14 18:49:03 +02:00
|
|
|
{
|
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs(" <tr valign=\"top\">\n", fout);
|
2006-06-14 18:49:03 +02:00
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
fprintf(fout, " <td align=\"%s\">", cont->aligns[(i) % cont->ncolumns] == 'r' ? "right" : "left");
|
2005-07-10 05:46:13 +02:00
|
|
|
/* is string only whitespace? */
|
2005-10-15 04:49:52 +02:00
|
|
|
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs(" ", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
|
|
|
html_escaped_print(*ptr, fout);
|
2005-07-10 05:46:13 +02:00
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs("</td>\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns == 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs(" </tr>\n", fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2003-06-12 09:52:51 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("</table>\n", fout);
|
|
|
|
|
|
|
|
/* print footers */
|
2012-05-01 22:03:45 +02:00
|
|
|
if (!opt_tuples_only && footers != NULL && !cancel_pressed)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("<p>", fout);
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("<br />\n", fout);
|
|
|
|
}
|
|
|
|
fputs("</p>", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
fputc('\n', fout);
|
2003-06-12 09:52:51 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_html_vertical(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
|
|
|
const char *opt_table_attr = cont->opt->tableAttr;
|
|
|
|
unsigned long record = cont->opt->prior_records + 1;
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fprintf(fout, "<table border=\"%d\"", opt_border);
|
|
|
|
if (opt_table_attr)
|
|
|
|
fprintf(fout, " %s", opt_table_attr);
|
|
|
|
fputs(">\n", fout);
|
|
|
|
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(" <caption>", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("</caption>\n", fout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout,
|
|
|
|
"\n <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n",
|
|
|
|
record++);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs("\n <tr><td colspan=\"2\"> </td></tr>\n", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs(" <tr valign=\"top\">\n"
|
1999-11-05 00:14:30 +01:00
|
|
|
" <th>", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(cont->headers[i % cont->ncolumns], fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs("</th>\n", fout);
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
fprintf(fout, " <td align=\"%s\">", cont->aligns[i % cont->ncolumns] == 'r' ? "right" : "left");
|
2005-07-10 05:46:13 +02:00
|
|
|
/* is string only whitespace? */
|
2005-10-15 04:49:52 +02:00
|
|
|
if ((*ptr)[strspn(*ptr, " \t")] == '\0')
|
2003-06-12 09:52:51 +02:00
|
|
|
fputs(" ", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
|
|
|
html_escaped_print(*ptr, fout);
|
2005-07-10 05:46:13 +02:00
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs("</td>\n </tr>\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2003-06-12 09:52:51 +02:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("</table>\n", fout);
|
|
|
|
|
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->footers != NULL && !cancel_pressed)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("<p>", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (f = cont->footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
html_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("<br />\n", fout);
|
|
|
|
}
|
|
|
|
fputs("</p>", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
|
|
|
|
fputc('\n', fout);
|
2003-06-12 09:52:51 +02:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*************************/
|
2006-08-30 00:25:08 +02:00
|
|
|
/* LaTeX */
|
1999-11-04 22:56:02 +01:00
|
|
|
/*************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
1999-11-05 00:14:30 +01:00
|
|
|
latex_escaped_print(const char *in, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
const char *p;
|
|
|
|
|
|
|
|
for (p = in; *p; p++)
|
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case '&':
|
|
|
|
fputs("\\&", fout);
|
|
|
|
break;
|
|
|
|
case '%':
|
|
|
|
fputs("\\%", fout);
|
|
|
|
break;
|
|
|
|
case '$':
|
|
|
|
fputs("\\$", fout);
|
|
|
|
break;
|
2004-08-06 20:09:15 +02:00
|
|
|
case '_':
|
|
|
|
fputs("\\_", fout);
|
|
|
|
break;
|
1999-11-05 00:14:30 +01:00
|
|
|
case '{':
|
|
|
|
fputs("\\{", fout);
|
|
|
|
break;
|
|
|
|
case '}':
|
|
|
|
fputs("\\}", fout);
|
|
|
|
break;
|
|
|
|
case '\\':
|
|
|
|
fputs("\\backslash", fout);
|
|
|
|
break;
|
|
|
|
case '\n':
|
|
|
|
fputs("\\\\", fout);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fputc(*p, fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_latex_text(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2013-01-17 17:39:38 +01:00
|
|
|
if (opt_border > 3)
|
|
|
|
opt_border = 3;
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs("\\begin{center}\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
latex_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\n\\end{center}\n\n", fout);
|
|
|
|
}
|
2004-08-06 20:09:15 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* begin environment and set alignments and borders */
|
|
|
|
fputs("\\begin{tabular}{", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2013-01-17 17:39:38 +01:00
|
|
|
if (opt_border >= 2)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("| ", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont->ncolumns; i++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
fputc(*(cont->aligns + i), fout);
|
|
|
|
if (opt_border != 0 && i < cont->ncolumns - 1)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" | ", fout);
|
|
|
|
}
|
2013-01-17 17:39:38 +01:00
|
|
|
if (opt_border >= 2)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" |", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("}\n", fout);
|
|
|
|
|
2013-01-17 17:39:38 +01:00
|
|
|
if (!opt_tuples_only && opt_border >= 2)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\\hline\n", fout);
|
|
|
|
|
|
|
|
/* print headers */
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
if (i != 0)
|
|
|
|
fputs(" & ", fout);
|
|
|
|
fputs("\\textit{", fout);
|
|
|
|
latex_escaped_print(*ptr, fout);
|
|
|
|
fputc('}', fout);
|
|
|
|
}
|
|
|
|
fputs(" \\\\\n", fout);
|
|
|
|
fputs("\\hline\n", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* print cells */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2010-03-01 21:55:45 +01:00
|
|
|
latex_escaped_print(*ptr, fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns == 0)
|
2006-06-14 18:49:03 +02:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs(" \\\\\n", fout);
|
2013-01-17 17:39:38 +01:00
|
|
|
if (opt_border == 3)
|
|
|
|
fputs("\\hline\n", fout);
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
|
|
|
fputs(" & ", fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
2013-01-18 14:30:31 +01:00
|
|
|
if (opt_border == 2)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\\hline\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\\end{tabular}\n\n\\noindent ", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2012-05-01 22:03:45 +02:00
|
|
|
if (footers && !opt_tuples_only && !cancel_pressed)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
latex_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" \\\\\n", fout);
|
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-17 17:39:38 +01:00
|
|
|
static void
|
2013-01-18 20:02:53 +01:00
|
|
|
print_latex_longtable_text(const printTableContent *cont, FILE *fout)
|
2013-01-17 17:39:38 +01:00
|
|
|
{
|
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
|
|
|
unsigned int i;
|
|
|
|
const char *opt_table_attr = cont->opt->tableAttr;
|
|
|
|
const char *next_opt_table_attr_char = opt_table_attr;
|
|
|
|
const char *last_opt_table_attr_char = NULL;
|
|
|
|
const char *const * ptr;
|
|
|
|
|
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (opt_border > 3)
|
|
|
|
opt_border = 3;
|
|
|
|
|
|
|
|
if (cont->opt->start_table)
|
|
|
|
{
|
|
|
|
/* begin environment and set alignments and borders */
|
|
|
|
fputs("\\begin{longtable}{", fout);
|
|
|
|
|
|
|
|
if (opt_border >= 2)
|
|
|
|
fputs("| ", fout);
|
|
|
|
|
|
|
|
for (i = 0; i < cont->ncolumns; i++)
|
|
|
|
{
|
|
|
|
/* longtable supports either a width (p) or an alignment (l/r) */
|
|
|
|
/* Are we left-justified and was a proportional width specified? */
|
|
|
|
if (*(cont->aligns + i) == 'l' && opt_table_attr)
|
|
|
|
{
|
|
|
|
#define LONGTABLE_WHITESPACE " \t\n"
|
|
|
|
|
|
|
|
/* advance over whitespace */
|
|
|
|
next_opt_table_attr_char += strspn(next_opt_table_attr_char,
|
|
|
|
LONGTABLE_WHITESPACE);
|
|
|
|
/* We have a value? */
|
|
|
|
if (next_opt_table_attr_char[0] != '\0')
|
|
|
|
{
|
|
|
|
fputs("p{", fout);
|
|
|
|
fwrite(next_opt_table_attr_char, strcspn(next_opt_table_attr_char,
|
2013-05-29 22:58:43 +02:00
|
|
|
LONGTABLE_WHITESPACE), 1, fout);
|
2013-01-17 17:39:38 +01:00
|
|
|
last_opt_table_attr_char = next_opt_table_attr_char;
|
|
|
|
next_opt_table_attr_char += strcspn(next_opt_table_attr_char,
|
2013-05-29 22:58:43 +02:00
|
|
|
LONGTABLE_WHITESPACE);
|
2013-01-17 17:39:38 +01:00
|
|
|
fputs("\\textwidth}", fout);
|
|
|
|
}
|
|
|
|
/* use previous value */
|
|
|
|
else if (last_opt_table_attr_char != NULL)
|
|
|
|
{
|
|
|
|
fputs("p{", fout);
|
|
|
|
fwrite(last_opt_table_attr_char, strcspn(last_opt_table_attr_char,
|
2013-05-29 22:58:43 +02:00
|
|
|
LONGTABLE_WHITESPACE), 1, fout);
|
2013-01-17 17:39:38 +01:00
|
|
|
fputs("\\textwidth}", fout);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
fputc('l', fout);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
fputc(*(cont->aligns + i), fout);
|
|
|
|
|
|
|
|
if (opt_border != 0 && i < cont->ncolumns - 1)
|
|
|
|
fputs(" | ", fout);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opt_border >= 2)
|
|
|
|
fputs(" |", fout);
|
|
|
|
|
|
|
|
fputs("}\n", fout);
|
|
|
|
|
|
|
|
/* print headers */
|
|
|
|
if (!opt_tuples_only)
|
|
|
|
{
|
|
|
|
/* firsthead */
|
|
|
|
if (opt_border >= 2)
|
|
|
|
fputs("\\toprule\n", fout);
|
|
|
|
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
|
|
|
|
{
|
|
|
|
if (i != 0)
|
|
|
|
fputs(" & ", fout);
|
|
|
|
fputs("\\small\\textbf{\\textit{", fout);
|
|
|
|
latex_escaped_print(*ptr, fout);
|
|
|
|
fputs("}}", fout);
|
|
|
|
}
|
|
|
|
fputs(" \\\\\n", fout);
|
|
|
|
fputs("\\midrule\n\\endfirsthead\n", fout);
|
|
|
|
|
|
|
|
/* secondary heads */
|
|
|
|
if (opt_border >= 2)
|
|
|
|
fputs("\\toprule\n", fout);
|
|
|
|
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
|
|
|
|
{
|
|
|
|
if (i != 0)
|
|
|
|
fputs(" & ", fout);
|
|
|
|
fputs("\\small\\textbf{\\textit{", fout);
|
|
|
|
latex_escaped_print(*ptr, fout);
|
|
|
|
fputs("}}", fout);
|
|
|
|
}
|
|
|
|
fputs(" \\\\\n", fout);
|
|
|
|
/* If the line under the row already appeared, don't do another */
|
|
|
|
if (opt_border != 3)
|
|
|
|
fputs("\\midrule\n", fout);
|
|
|
|
fputs("\\endhead\n", fout);
|
|
|
|
|
|
|
|
/* table name, caption? */
|
|
|
|
if (!opt_tuples_only && cont->title)
|
|
|
|
{
|
|
|
|
/* Don't output if we are printing a line under each row */
|
|
|
|
if (opt_border == 2)
|
|
|
|
fputs("\\bottomrule\n", fout);
|
|
|
|
fputs("\\caption[", fout);
|
|
|
|
latex_escaped_print(cont->title, fout);
|
|
|
|
fputs(" (Continued)]{", fout);
|
|
|
|
latex_escaped_print(cont->title, fout);
|
|
|
|
fputs("}\n\\endfoot\n", fout);
|
|
|
|
if (opt_border == 2)
|
|
|
|
fputs("\\bottomrule\n", fout);
|
|
|
|
fputs("\\caption[", fout);
|
|
|
|
latex_escaped_print(cont->title, fout);
|
|
|
|
fputs("]{", fout);
|
|
|
|
latex_escaped_print(cont->title, fout);
|
|
|
|
fputs("}\n\\endlastfoot\n", fout);
|
|
|
|
}
|
|
|
|
/* output bottom table line? */
|
|
|
|
else if (opt_border >= 2)
|
|
|
|
{
|
|
|
|
fputs("\\bottomrule\n\\endfoot\n", fout);
|
|
|
|
fputs("\\bottomrule\n\\endlastfoot\n", fout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* print cells */
|
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
|
|
|
{
|
|
|
|
/* Add a line under each row? */
|
|
|
|
if (i != 0 && i % cont->ncolumns != 0)
|
|
|
|
fputs("\n&\n", fout);
|
|
|
|
fputs("\\raggedright{", fout);
|
|
|
|
latex_escaped_print(*ptr, fout);
|
|
|
|
fputc('}', fout);
|
|
|
|
if ((i + 1) % cont->ncolumns == 0)
|
|
|
|
{
|
|
|
|
fputs(" \\tabularnewline\n", fout);
|
|
|
|
if (opt_border == 3)
|
|
|
|
fputs(" \\hline\n", fout);
|
|
|
|
}
|
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cont->opt->stop_table)
|
|
|
|
fputs("\\end{longtable}\n", fout);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-11-04 22:56:02 +01:00
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_latex_vertical(const printTableContent *cont, FILE *fout)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
|
|
|
unsigned long record = cont->opt->prior_records + 1;
|
1999-11-05 00:14:30 +01:00
|
|
|
unsigned int i;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs("\\begin{center}\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
latex_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\n\\end{center}\n\n", fout);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* begin environment and set alignments and borders */
|
|
|
|
fputs("\\begin{tabular}{", fout);
|
|
|
|
if (opt_border == 0)
|
|
|
|
fputs("cl", fout);
|
|
|
|
else if (opt_border == 1)
|
|
|
|
fputs("c|l", fout);
|
|
|
|
else if (opt_border == 2)
|
|
|
|
fputs("|c|l|", fout);
|
|
|
|
fputs("}\n", fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
1999-11-05 00:14:30 +01:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
/* new record */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
if (opt_border == 2)
|
2004-08-06 20:09:15 +02:00
|
|
|
{
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs("\\hline\n", fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++);
|
2004-08-06 20:09:15 +02:00
|
|
|
}
|
|
|
|
else
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %lu}} \\\\\n", record++);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
if (opt_border >= 1)
|
|
|
|
fputs("\\hline\n", fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
latex_escaped_print(cont->headers[i % cont->ncolumns], fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
fputs(" & ", fout);
|
|
|
|
latex_escaped_print(*ptr, fout);
|
|
|
|
fputs(" \\\\\n", fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
if (opt_border == 2)
|
|
|
|
fputs("\\hline\n", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\\end{tabular}\n\n\\noindent ", fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->footers && !opt_tuples_only && !cancel_pressed)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableFooter *f;
|
|
|
|
|
|
|
|
for (f = cont->footers; f; f = f->next)
|
2005-07-14 09:32:01 +02:00
|
|
|
{
|
2010-03-01 21:55:45 +01:00
|
|
|
latex_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" \\\\\n", fout);
|
2005-07-14 09:32:01 +02:00
|
|
|
}
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-09 17:27:27 +02:00
|
|
|
/*************************/
|
|
|
|
/* Troff -ms */
|
|
|
|
/*************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
troff_ms_escaped_print(const char *in, FILE *fout)
|
|
|
|
{
|
|
|
|
const char *p;
|
|
|
|
|
|
|
|
for (p = in; *p; p++)
|
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case '\\':
|
2005-06-09 20:40:06 +02:00
|
|
|
fputs("\\(rs", fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fputc(*p, fout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_troff_ms_text(const printTableContent *cont, FILE *fout)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
2005-06-09 17:27:27 +02:00
|
|
|
unsigned int i;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(".LP\n.DS C\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\n.DE\n", fout);
|
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* begin environment and set alignments and borders */
|
|
|
|
fputs(".LP\n.TS\n", fout);
|
|
|
|
if (opt_border == 2)
|
|
|
|
fputs("center box;\n", fout);
|
|
|
|
else
|
|
|
|
fputs("center;\n", fout);
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont->ncolumns; i++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
fputc(*(cont->aligns + i), fout);
|
|
|
|
if (opt_border > 0 && i < cont->ncolumns - 1)
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(" | ", fout);
|
|
|
|
}
|
|
|
|
fputs(".\n", fout);
|
|
|
|
|
|
|
|
/* print headers */
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->headers; i < cont->ncolumns; i++, ptr++)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
if (i != 0)
|
|
|
|
fputc('\t', fout);
|
|
|
|
fputs("\\fI", fout);
|
|
|
|
troff_ms_escaped_print(*ptr, fout);
|
|
|
|
fputs("\\fP", fout);
|
|
|
|
}
|
|
|
|
fputs("\n_\n", fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* print cells */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2010-03-01 21:55:45 +01:00
|
|
|
troff_ms_escaped_print(*ptr, fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if ((i + 1) % cont->ncolumns == 0)
|
2006-06-14 18:49:03 +02:00
|
|
|
{
|
2005-06-09 17:27:27 +02:00
|
|
|
fputc('\n', fout);
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
else
|
|
|
|
fputc('\t', fout);
|
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2012-05-01 22:03:45 +02:00
|
|
|
printTableFooter *footers = footers_with_default(cont);
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(".TE\n.DS L\n", fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2012-05-01 22:03:45 +02:00
|
|
|
if (footers && !opt_tuples_only && !cancel_pressed)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-05-01 22:03:45 +02:00
|
|
|
for (f = footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(".DE\n", fout);
|
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2008-05-13 00:59:58 +02:00
|
|
|
print_troff_ms_vertical(const printTableContent *cont, FILE *fout)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
bool opt_tuples_only = cont->opt->tuples_only;
|
|
|
|
unsigned short opt_border = cont->opt->border;
|
|
|
|
unsigned long record = cont->opt->prior_records + 1;
|
2005-06-09 17:27:27 +02:00
|
|
|
unsigned int i;
|
2005-10-15 04:49:52 +02:00
|
|
|
const char *const * ptr;
|
|
|
|
unsigned short current_format = 0; /* 0=none, 1=header, 2=body */
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border > 2)
|
|
|
|
opt_border = 2;
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->start_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
/* print title */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (!opt_tuples_only && cont->title)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(".LP\n.DS C\n", fout);
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(cont->title, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs("\n.DE\n", fout);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* begin environment and set alignments and borders */
|
|
|
|
fputs(".LP\n.TS\n", fout);
|
|
|
|
if (opt_border == 2)
|
|
|
|
fputs("center box;\n", fout);
|
|
|
|
else
|
|
|
|
fputs("center;\n", fout);
|
|
|
|
|
|
|
|
/* basic format */
|
|
|
|
if (opt_tuples_only)
|
|
|
|
fputs("c l;\n", fout);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
current_format = 2; /* assume tuples printed already */
|
|
|
|
|
2005-06-09 17:27:27 +02:00
|
|
|
/* print records */
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
|
|
|
/* new record */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (i % cont->ncolumns == 0)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
break;
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
|
|
|
if (current_format != 1)
|
|
|
|
{
|
2006-08-30 00:25:08 +02:00
|
|
|
if (opt_border == 2 && record > 1)
|
2005-06-09 17:27:27 +02:00
|
|
|
fputs("_\n", fout);
|
|
|
|
if (current_format != 0)
|
|
|
|
fputs(".T&\n", fout);
|
|
|
|
fputs("c s.\n", fout);
|
|
|
|
current_format = 1;
|
|
|
|
}
|
2006-08-30 00:25:08 +02:00
|
|
|
fprintf(fout, "\\fIRecord %lu\\fP\n", record++);
|
2005-06-09 17:27:27 +02:00
|
|
|
}
|
|
|
|
if (opt_border >= 1)
|
|
|
|
fputs("_\n", fout);
|
|
|
|
}
|
|
|
|
|
2005-07-14 08:49:58 +02:00
|
|
|
if (!opt_tuples_only)
|
2005-06-09 17:27:27 +02:00
|
|
|
{
|
|
|
|
if (current_format != 2)
|
|
|
|
{
|
|
|
|
if (current_format != 0)
|
|
|
|
fputs(".T&\n", fout);
|
|
|
|
if (opt_border != 1)
|
|
|
|
fputs("c l.\n", fout);
|
|
|
|
else
|
|
|
|
fputs("c | l.\n", fout);
|
|
|
|
current_format = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(cont->headers[i % cont->ncolumns], fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
fputc('\t', fout);
|
2010-03-01 21:55:45 +01:00
|
|
|
troff_ms_escaped_print(*ptr, fout);
|
2005-07-14 09:32:01 +02:00
|
|
|
|
2005-06-09 17:27:27 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->opt->stop_table)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
|
|
|
fputs(".TE\n.DS L\n", fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/* print footers */
|
2008-05-13 00:59:58 +02:00
|
|
|
if (cont->footers && !opt_tuples_only && !cancel_pressed)
|
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
|
|
|
for (f = cont->footers; f; f = f->next)
|
2006-08-30 00:25:08 +02:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
troff_ms_escaped_print(f->data, fout);
|
2006-08-30 00:25:08 +02:00
|
|
|
fputc('\n', fout);
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
fputs(".DE\n", fout);
|
|
|
|
}
|
2005-06-09 17:27:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-11-04 22:56:02 +01:00
|
|
|
/********************************/
|
* Includes tab completion. It's not magic, but it's very cool. At any
rate
it's better than what used to be there.
* Does proper SQL "host variable" substitution as pointed out by Andreas
Zeugwetter (thanks): select * from :foo; Also some changes in how ':'
and ';' are treated (escape with \ to send to backend). This does
_not_
affect the '::' cast operator, but perhaps others that contain : or ;
(but there are none right now).
* To show description with a <something> listing, append '?' to command
name, e.g., \df?. This seemed to be the convenient and logical
solution.
Or append a '+' to see more useless information, e.g., \df+.
* Fixed fflush()'ing bug pointed out by Jan during the regression test
discussion.
* Added LastOid variable. This ought to take care of TODO item "Add a
function to return the last inserted oid, for use in psql scripts"
(under CLIENTS)
E.g.,
insert into foo values(...);
insert into bar values(..., :LastOid);
\echo $LastOid
* \d command shows constraints, rules, and triggers defined on the table
(in addition to indices)
* Various fixes, optimizations, corrections
* Documentation update as well
Note: This now requires snprintf(), which, if necessary, is taken from
src/backend/port. This is certainly a little weird, but it should
suffice
until a source tree cleanup is done.
Enjoy.
--
Peter Eisentraut Sernanders väg 10:115
1999-11-26 05:24:17 +01:00
|
|
|
/* Public functions */
|
1999-11-04 22:56:02 +01:00
|
|
|
/********************************/
|
|
|
|
|
|
|
|
|
2003-03-18 23:15:44 +01:00
|
|
|
/*
|
|
|
|
* PageOutput
|
|
|
|
*
|
|
|
|
* Tests if pager is needed and returns appropriate FILE pointer.
|
|
|
|
*/
|
|
|
|
FILE *
|
|
|
|
PageOutput(int lines, unsigned short int pager)
|
|
|
|
{
|
|
|
|
/* check whether we need / can / are supposed to use pager */
|
2008-05-18 01:34:44 +02:00
|
|
|
if (pager && isatty(fileno(stdin)) && isatty(fileno(stdout)))
|
2003-03-18 23:15:44 +01:00
|
|
|
{
|
|
|
|
const char *pagerprog;
|
2006-10-04 02:30:14 +02:00
|
|
|
FILE *pagerpipe;
|
2003-03-18 23:15:44 +01:00
|
|
|
|
|
|
|
#ifdef TIOCGWINSZ
|
|
|
|
int result;
|
|
|
|
struct winsize screen_size;
|
|
|
|
|
|
|
|
result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
|
2003-07-25 23:48:45 +02:00
|
|
|
|
|
|
|
/* >= accounts for a one-line prompt */
|
|
|
|
if (result == -1 || lines >= screen_size.ws_row || pager > 1)
|
2003-03-18 23:15:44 +01:00
|
|
|
{
|
|
|
|
#endif
|
|
|
|
pagerprog = getenv("PAGER");
|
|
|
|
if (!pagerprog)
|
|
|
|
pagerprog = DEFAULT_PAGER;
|
|
|
|
#ifndef WIN32
|
|
|
|
pqsignal(SIGPIPE, SIG_IGN);
|
|
|
|
#endif
|
2006-08-30 00:25:08 +02:00
|
|
|
pagerpipe = popen(pagerprog, "w");
|
|
|
|
if (pagerpipe)
|
|
|
|
return pagerpipe;
|
2003-03-18 23:15:44 +01:00
|
|
|
#ifdef TIOCGWINSZ
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return stdout;
|
|
|
|
}
|
|
|
|
|
2006-08-30 00:25:08 +02:00
|
|
|
/*
|
|
|
|
* ClosePager
|
|
|
|
*
|
|
|
|
* Close previously opened pager pipe, if any
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ClosePager(FILE *pagerpipe)
|
|
|
|
{
|
|
|
|
if (pagerpipe && pagerpipe != stdout)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If printing was canceled midstream, warn about it.
|
|
|
|
*
|
2006-10-04 02:30:14 +02:00
|
|
|
* Some pagers like less use Ctrl-C as part of their command set. Even
|
|
|
|
* so, we abort our processing and warn the user what we did. If the
|
|
|
|
* pager quit as a result of the SIGINT, this message won't go
|
|
|
|
* anywhere ...
|
2006-08-30 00:25:08 +02:00
|
|
|
*/
|
|
|
|
if (cancel_pressed)
|
|
|
|
fprintf(pagerpipe, _("Interrupted\n"));
|
|
|
|
|
|
|
|
pclose(pagerpipe);
|
|
|
|
#ifndef WIN32
|
|
|
|
pqsignal(SIGPIPE, SIG_DFL);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
2003-03-18 23:15:44 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/*
|
|
|
|
* Initialise a table contents struct.
|
2008-05-13 02:14:11 +02:00
|
|
|
* Must be called before any other printTable method is used.
|
2008-05-13 00:59:58 +02:00
|
|
|
*
|
2008-05-13 02:14:11 +02:00
|
|
|
* The title is not duplicated; the caller must ensure that the buffer
|
|
|
|
* is available for the lifetime of the printTableContent struct.
|
2008-05-13 00:59:58 +02:00
|
|
|
*
|
|
|
|
* If you call this, you must call printTableCleanup once you're done with the
|
|
|
|
* table.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
printTableInit(printTableContent *const content, const printTableOpt *opt,
|
2008-05-21 18:00:10 +02:00
|
|
|
const char *title, const int ncolumns, const int nrows)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
content->opt = opt;
|
|
|
|
content->title = title;
|
|
|
|
content->ncolumns = ncolumns;
|
|
|
|
content->nrows = nrows;
|
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
content->headers = pg_malloc0((ncolumns + 1) * sizeof(*content->headers));
|
2008-05-13 00:59:58 +02:00
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
content->cells = pg_malloc0((ncolumns * nrows + 1) * sizeof(*content->cells));
|
2008-05-13 00:59:58 +02:00
|
|
|
|
2010-03-01 21:55:45 +01:00
|
|
|
content->cellmustfree = NULL;
|
2008-05-13 00:59:58 +02:00
|
|
|
content->footers = NULL;
|
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
content->aligns = pg_malloc0((ncolumns + 1) * sizeof(*content->align));
|
2008-05-13 00:59:58 +02:00
|
|
|
|
|
|
|
content->header = content->headers;
|
|
|
|
content->cell = content->cells;
|
|
|
|
content->footer = content->footers;
|
|
|
|
content->align = content->aligns;
|
2010-03-01 21:55:45 +01:00
|
|
|
content->cellsadded = 0;
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
2003-03-18 23:15:44 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/*
|
|
|
|
* Add a header to the table.
|
|
|
|
*
|
|
|
|
* Headers are not duplicated; you must ensure that the header string is
|
|
|
|
* available for the lifetime of the printTableContent struct.
|
|
|
|
*
|
|
|
|
* If translate is true, the function will pass the header through gettext.
|
|
|
|
* Otherwise, the header will not be translated.
|
|
|
|
*
|
|
|
|
* align is either 'l' or 'r', and specifies the alignment for cells in this
|
|
|
|
* column.
|
|
|
|
*/
|
1999-11-04 22:56:02 +01:00
|
|
|
void
|
2012-03-16 19:03:38 +01:00
|
|
|
printTableAddHeader(printTableContent *const content, char *header,
|
2008-05-21 18:00:10 +02:00
|
|
|
const bool translate, const char align)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
#ifndef ENABLE_NLS
|
2009-06-11 16:49:15 +02:00
|
|
|
(void) translate; /* unused parameter */
|
2008-05-13 00:59:58 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if (content->header >= content->headers + content->ncolumns)
|
|
|
|
{
|
|
|
|
fprintf(stderr, _("Cannot add header to table content: "
|
|
|
|
"column count of %d exceeded.\n"),
|
|
|
|
content->ncolumns);
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
*content->header = (char *) mbvalidate((unsigned char *) header,
|
|
|
|
content->opt->encoding);
|
|
|
|
#ifdef ENABLE_NLS
|
|
|
|
if (translate)
|
|
|
|
*content->header = _(*content->header);
|
|
|
|
#endif
|
|
|
|
content->header++;
|
|
|
|
|
|
|
|
*content->align = align;
|
|
|
|
content->align++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add a cell to the table.
|
|
|
|
*
|
|
|
|
* Cells are not duplicated; you must ensure that the cell string is available
|
|
|
|
* for the lifetime of the printTableContent struct.
|
|
|
|
*
|
|
|
|
* If translate is true, the function will pass the cell through gettext.
|
|
|
|
* Otherwise, the cell will not be translated.
|
2010-03-01 21:55:45 +01:00
|
|
|
*
|
|
|
|
* If mustfree is true, the cell string is freed by printTableCleanup().
|
2010-03-01 22:27:26 +01:00
|
|
|
* Note: Automatic freeing of translatable strings is not supported.
|
2008-05-13 00:59:58 +02:00
|
|
|
*/
|
|
|
|
void
|
2012-03-16 19:03:38 +01:00
|
|
|
printTableAddCell(printTableContent *const content, char *cell,
|
2010-03-01 21:55:45 +01:00
|
|
|
const bool translate, const bool mustfree)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
#ifndef ENABLE_NLS
|
2009-06-11 16:49:15 +02:00
|
|
|
(void) translate; /* unused parameter */
|
2008-05-13 00:59:58 +02:00
|
|
|
#endif
|
|
|
|
|
2010-03-01 21:55:45 +01:00
|
|
|
if (content->cellsadded >= content->ncolumns * content->nrows)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, _("Cannot add cell to table content: "
|
|
|
|
"total cell count of %d exceeded.\n"),
|
|
|
|
content->ncolumns * content->nrows);
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
*content->cell = (char *) mbvalidate((unsigned char *) cell,
|
|
|
|
content->opt->encoding);
|
|
|
|
|
|
|
|
#ifdef ENABLE_NLS
|
|
|
|
if (translate)
|
2010-03-01 22:27:26 +01:00
|
|
|
*content->cell = _(*content->cell);
|
2008-05-13 00:59:58 +02:00
|
|
|
#endif
|
2010-03-01 21:55:45 +01:00
|
|
|
|
|
|
|
if (mustfree)
|
|
|
|
{
|
|
|
|
if (content->cellmustfree == NULL)
|
2012-10-02 21:35:10 +02:00
|
|
|
content->cellmustfree =
|
|
|
|
pg_malloc0((content->ncolumns * content->nrows + 1) * sizeof(bool));
|
2010-03-01 21:55:45 +01:00
|
|
|
|
|
|
|
content->cellmustfree[content->cellsadded] = true;
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
content->cell++;
|
2010-03-01 21:55:45 +01:00
|
|
|
content->cellsadded++;
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add a footer to the table.
|
|
|
|
*
|
|
|
|
* Footers are added as elements of a singly-linked list, and the content is
|
|
|
|
* strdup'd, so there is no need to keep the original footer string around.
|
|
|
|
*
|
|
|
|
* Footers are never translated by the function. If you want the footer
|
2014-05-06 18:12:18 +02:00
|
|
|
* translated you must do so yourself, before calling printTableAddFooter. The
|
2008-05-13 00:59:58 +02:00
|
|
|
* reason this works differently to headers and cells is that footers tend to
|
|
|
|
* be made of up individually translated components, rather than being
|
|
|
|
* translated as a whole.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
printTableAddFooter(printTableContent *const content, const char *footer)
|
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
2012-10-02 21:35:10 +02:00
|
|
|
f = pg_malloc0(sizeof(*f));
|
2008-05-13 00:59:58 +02:00
|
|
|
f->data = pg_strdup(footer);
|
|
|
|
|
|
|
|
if (content->footers == NULL)
|
|
|
|
content->footers = f;
|
|
|
|
else
|
|
|
|
content->footer->next = f;
|
|
|
|
|
|
|
|
content->footer = f;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Change the content of the last-added footer.
|
|
|
|
*
|
|
|
|
* The current contents of the last-added footer are freed, and replaced by the
|
|
|
|
* content given in *footer. If there was no previous footer, add a new one.
|
|
|
|
*
|
|
|
|
* The content is strdup'd, so there is no need to keep the original string
|
|
|
|
* around.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
printTableSetFooter(printTableContent *const content, const char *footer)
|
|
|
|
{
|
|
|
|
if (content->footers != NULL)
|
|
|
|
{
|
|
|
|
free(content->footer->data);
|
|
|
|
content->footer->data = pg_strdup(footer);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
printTableAddFooter(content, footer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Free all memory allocated to this struct.
|
|
|
|
*
|
|
|
|
* Once this has been called, the struct is unusable unless you pass it to
|
|
|
|
* printTableInit() again.
|
|
|
|
*/
|
|
|
|
void
|
2008-05-21 18:00:10 +02:00
|
|
|
printTableCleanup(printTableContent *const content)
|
2008-05-13 00:59:58 +02:00
|
|
|
{
|
2010-03-01 21:55:45 +01:00
|
|
|
if (content->cellmustfree)
|
|
|
|
{
|
2010-07-06 21:19:02 +02:00
|
|
|
int i;
|
|
|
|
|
2010-03-01 21:55:45 +01:00
|
|
|
for (i = 0; i < content->nrows * content->ncolumns; i++)
|
|
|
|
{
|
|
|
|
if (content->cellmustfree[i])
|
|
|
|
free((char *) content->cells[i]);
|
|
|
|
}
|
|
|
|
free(content->cellmustfree);
|
|
|
|
content->cellmustfree = NULL;
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
free(content->headers);
|
|
|
|
free(content->cells);
|
|
|
|
free(content->aligns);
|
|
|
|
|
|
|
|
content->opt = NULL;
|
|
|
|
content->title = NULL;
|
|
|
|
content->headers = NULL;
|
|
|
|
content->cells = NULL;
|
|
|
|
content->aligns = NULL;
|
|
|
|
content->header = NULL;
|
|
|
|
content->cell = NULL;
|
|
|
|
content->align = NULL;
|
|
|
|
|
|
|
|
if (content->footers)
|
|
|
|
{
|
|
|
|
for (content->footer = content->footers; content->footer;)
|
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
|
|
|
f = content->footer;
|
|
|
|
content->footer = f->next;
|
|
|
|
free(f->data);
|
|
|
|
free(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
content->footers = NULL;
|
|
|
|
content->footer = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2008-05-16 18:59:05 +02:00
|
|
|
* IsPagerNeeded
|
|
|
|
*
|
|
|
|
* Setup pager if required
|
2008-05-13 00:59:58 +02:00
|
|
|
*/
|
2009-06-12 18:17:29 +02:00
|
|
|
static void
|
2011-11-12 16:03:10 +01:00
|
|
|
IsPagerNeeded(const printTableContent *cont, const int extra_lines, bool expanded, FILE **fout,
|
2008-05-16 18:59:05 +02:00
|
|
|
bool *is_pager)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-16 18:59:05 +02:00
|
|
|
if (*fout == stdout)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
int lines;
|
|
|
|
|
2011-11-12 16:03:10 +01:00
|
|
|
if (expanded)
|
2008-05-13 00:59:58 +02:00
|
|
|
lines = (cont->ncolumns + 1) * cont->nrows;
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-13 00:59:58 +02:00
|
|
|
lines = cont->nrows + 1;
|
|
|
|
|
|
|
|
if (!cont->opt->tuples_only)
|
|
|
|
{
|
|
|
|
printTableFooter *f;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* FIXME -- this is slightly bogus: it counts the number of
|
|
|
|
* footers, not the number of lines in them.
|
|
|
|
*/
|
|
|
|
for (f = cont->footers; f; f = f->next)
|
2002-09-02 01:30:46 +02:00
|
|
|
lines++;
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
*fout = PageOutput(lines + extra_lines, cont->opt->pager);
|
|
|
|
*is_pager = (*fout != stdout);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
*is_pager = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use this to print just any table in the supported formats.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
printTable(const printTableContent *cont, FILE *fout, FILE *flog)
|
|
|
|
{
|
|
|
|
bool is_pager = false;
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-16 18:59:05 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (cont->opt->format == PRINT_NOTHING)
|
|
|
|
return;
|
|
|
|
|
2011-11-12 16:03:10 +01:00
|
|
|
/* print_aligned_*() handles the pager themselves */
|
|
|
|
if (cont->opt->format != PRINT_ALIGNED &&
|
|
|
|
cont->opt->format != PRINT_WRAPPED)
|
|
|
|
IsPagerNeeded(cont, 0, (cont->opt->expanded == 1), &fout, &is_pager);
|
1999-11-05 00:14:30 +01:00
|
|
|
|
|
|
|
/* print the stuff */
|
|
|
|
|
2005-06-14 04:57:45 +02:00
|
|
|
if (flog)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_aligned_text(cont, flog);
|
2005-06-14 04:57:45 +02:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
switch (cont->opt->format)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
|
|
|
case PRINT_UNALIGNED:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_unaligned_vertical(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_unaligned_text(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
break;
|
|
|
|
case PRINT_ALIGNED:
|
2008-05-08 19:04:26 +02:00
|
|
|
case PRINT_WRAPPED:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_aligned_vertical(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_aligned_text(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
break;
|
|
|
|
case PRINT_HTML:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_html_vertical(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_html_text(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
break;
|
|
|
|
case PRINT_LATEX:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_latex_vertical(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_latex_text(cont, fout);
|
1999-11-05 00:14:30 +01:00
|
|
|
break;
|
2013-01-17 17:39:38 +01:00
|
|
|
case PRINT_LATEX_LONGTABLE:
|
|
|
|
if (cont->opt->expanded == 1)
|
|
|
|
print_latex_vertical(cont, fout);
|
|
|
|
else
|
2013-01-18 20:02:53 +01:00
|
|
|
print_latex_longtable_text(cont, fout);
|
2013-01-17 17:39:38 +01:00
|
|
|
break;
|
2005-06-09 17:27:27 +02:00
|
|
|
case PRINT_TROFF_MS:
|
2011-11-12 16:03:10 +01:00
|
|
|
if (cont->opt->expanded == 1)
|
2008-05-16 18:59:05 +02:00
|
|
|
print_troff_ms_vertical(cont, fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
else
|
2008-05-16 18:59:05 +02:00
|
|
|
print_troff_ms_text(cont, fout);
|
2005-06-09 17:27:27 +02:00
|
|
|
break;
|
1999-11-05 00:14:30 +01:00
|
|
|
default:
|
2009-04-11 16:11:45 +02:00
|
|
|
fprintf(stderr, _("invalid output format (internal error): %d"),
|
2008-05-13 00:59:58 +02:00
|
|
|
cont->opt->format);
|
2005-06-13 08:36:22 +02:00
|
|
|
exit(EXIT_FAILURE);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
if (is_pager)
|
2008-05-16 18:59:05 +02:00
|
|
|
ClosePager(fout);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/*
|
|
|
|
* Use this to print query results
|
|
|
|
*
|
|
|
|
* It calls printTable with all the things set straight.
|
|
|
|
*/
|
1999-11-04 22:56:02 +01:00
|
|
|
void
|
2005-06-14 04:57:45 +02:00
|
|
|
printQuery(const PGresult *result, const printQueryOpt *opt, FILE *fout, FILE *flog)
|
1999-11-04 22:56:02 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableContent cont;
|
2007-12-12 22:41:47 +01:00
|
|
|
int i,
|
|
|
|
r,
|
|
|
|
c;
|
1999-11-05 00:14:30 +01:00
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
if (cancel_pressed)
|
|
|
|
return;
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
printTableInit(&cont, &opt->topt, opt->title,
|
|
|
|
PQnfields(result), PQntuples(result));
|
1999-11-04 22:56:02 +01:00
|
|
|
|
Fix translatability markings in psql, and add defenses against future bugs.
Several previous commits have added columns to various \d queries without
updating their translate_columns[] arrays, leading to potentially incorrect
translations in NLS-enabled builds. Offenders include commit 893686762
(added prosecdef to \df+), c9ac00e6e (added description to \dc+) and
3b17efdfd (added description to \dC+). Fix those cases back to 9.3 or
9.2 as appropriate.
Since this is evidently more easily missed than one would like, in HEAD
also add an Assert that the supplied array is long enough. This requires
an API change for printQuery(), so it seems inappropriate for back
branches, but presumably all future changes will be tested in HEAD anyway.
In HEAD and 9.3, also clean up a whole lot of sloppiness in the emitted
SQL for \dy (event triggers): lack of translatability due to failing to
pass words-to-be-translated through gettext_noop(), inadequate schema
qualification, and sloppy formatting resulting in unnecessarily ugly
-E output.
Peter Eisentraut and Tom Lane, per bug #8702 from Sergey Burladyan
2014-01-04 22:05:16 +01:00
|
|
|
/* Assert caller supplied enough translate_columns[] entries */
|
|
|
|
Assert(opt->translate_columns == NULL ||
|
|
|
|
opt->n_translate_columns >= cont.ncolumns);
|
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
for (i = 0; i < cont.ncolumns; i++)
|
1999-11-05 00:14:30 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
char align;
|
1999-11-05 00:14:30 +01:00
|
|
|
Oid ftype = PQftype(result, i);
|
|
|
|
|
2006-06-14 18:49:03 +02:00
|
|
|
switch (ftype)
|
|
|
|
{
|
|
|
|
case INT2OID:
|
|
|
|
case INT4OID:
|
|
|
|
case INT8OID:
|
|
|
|
case FLOAT4OID:
|
|
|
|
case FLOAT8OID:
|
|
|
|
case NUMERICOID:
|
|
|
|
case OIDOID:
|
|
|
|
case XIDOID:
|
|
|
|
case CIDOID:
|
|
|
|
case CASHOID:
|
2008-05-13 00:59:58 +02:00
|
|
|
align = 'r';
|
2006-06-14 18:49:03 +02:00
|
|
|
break;
|
|
|
|
default:
|
2008-05-13 00:59:58 +02:00
|
|
|
align = 'l';
|
2006-06-14 18:49:03 +02:00
|
|
|
break;
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
|
|
|
|
printTableAddHeader(&cont, PQfname(result, i),
|
2008-07-15 00:00:04 +02:00
|
|
|
opt->translate_header, align);
|
1999-11-05 00:14:30 +01:00
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/* set cells */
|
|
|
|
for (r = 0; r < cont.nrows; r++)
|
|
|
|
{
|
|
|
|
for (c = 0; c < cont.ncolumns; c++)
|
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
char *cell;
|
2010-03-01 21:55:45 +01:00
|
|
|
bool mustfree = false;
|
2009-06-11 16:49:15 +02:00
|
|
|
bool translate;
|
2008-05-13 00:59:58 +02:00
|
|
|
|
|
|
|
if (PQgetisnull(result, r, c))
|
|
|
|
cell = opt->nullPrint ? opt->nullPrint : "";
|
|
|
|
else
|
2010-03-01 21:55:45 +01:00
|
|
|
{
|
2008-05-13 00:59:58 +02:00
|
|
|
cell = PQgetvalue(result, r, c);
|
2010-03-01 21:55:45 +01:00
|
|
|
if (cont.aligns[c] == 'r' && opt->topt.numericLocale)
|
|
|
|
{
|
|
|
|
cell = format_numeric_locale(cell);
|
|
|
|
mustfree = true;
|
|
|
|
}
|
|
|
|
}
|
2008-05-13 00:59:58 +02:00
|
|
|
|
2008-07-15 00:00:04 +02:00
|
|
|
translate = (opt->translate_columns && opt->translate_columns[c]);
|
2010-03-01 21:55:45 +01:00
|
|
|
printTableAddCell(&cont, cell, translate, mustfree);
|
2008-05-13 00:59:58 +02:00
|
|
|
}
|
|
|
|
}
|
1999-11-04 22:56:02 +01:00
|
|
|
|
2008-05-13 00:59:58 +02:00
|
|
|
/* set footers */
|
|
|
|
if (opt->footers)
|
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
char **footer;
|
2008-05-13 00:59:58 +02:00
|
|
|
|
|
|
|
for (footer = opt->footers; *footer; footer++)
|
|
|
|
printTableAddFooter(&cont, *footer);
|
|
|
|
}
|
|
|
|
|
|
|
|
printTable(&cont, fout, flog);
|
|
|
|
printTableCleanup(&cont);
|
1999-11-04 22:56:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-07-14 10:42:37 +02:00
|
|
|
void
|
|
|
|
setDecimalLocale(void)
|
|
|
|
{
|
|
|
|
struct lconv *extlconv;
|
|
|
|
|
|
|
|
extlconv = localeconv();
|
|
|
|
|
|
|
|
if (*extlconv->decimal_point)
|
2008-05-13 00:59:58 +02:00
|
|
|
decimal_point = pg_strdup(extlconv->decimal_point);
|
2005-07-14 10:42:37 +02:00
|
|
|
else
|
|
|
|
decimal_point = "."; /* SQL output standard */
|
|
|
|
if (*extlconv->grouping && atoi(extlconv->grouping) > 0)
|
2008-05-13 00:59:58 +02:00
|
|
|
grouping = pg_strdup(extlconv->grouping);
|
2005-07-14 10:42:37 +02:00
|
|
|
else
|
2005-10-15 04:49:52 +02:00
|
|
|
grouping = "3"; /* most common */
|
2007-11-22 18:51:39 +01:00
|
|
|
|
|
|
|
/* similar code exists in formatting.c */
|
2005-07-14 10:42:37 +02:00
|
|
|
if (*extlconv->thousands_sep)
|
2008-05-13 00:59:58 +02:00
|
|
|
thousands_sep = pg_strdup(extlconv->thousands_sep);
|
2007-11-22 16:10:05 +01:00
|
|
|
/* Make sure thousands separator doesn't match decimal point symbol. */
|
2007-11-21 23:28:18 +01:00
|
|
|
else if (strcmp(decimal_point, ",") != 0)
|
2005-07-14 17:54:21 +02:00
|
|
|
thousands_sep = ",";
|
2005-07-14 10:42:37 +02:00
|
|
|
else
|
2005-07-14 17:54:21 +02:00
|
|
|
thousands_sep = ".";
|
2005-07-14 10:42:37 +02:00
|
|
|
}
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2009-10-13 23:04:01 +02:00
|
|
|
/* get selected or default line style */
|
|
|
|
const printTextFormat *
|
|
|
|
get_line_style(const printTableOpt *opt)
|
|
|
|
{
|
2009-11-25 21:26:31 +01:00
|
|
|
/*
|
2010-02-26 03:01:40 +01:00
|
|
|
* Note: this function mainly exists to preserve the convention that a
|
|
|
|
* printTableOpt struct can be initialized to zeroes to get default
|
2009-11-25 21:26:31 +01:00
|
|
|
* behavior.
|
|
|
|
*/
|
2009-10-13 23:04:01 +02:00
|
|
|
if (opt->line_style != NULL)
|
|
|
|
return opt->line_style;
|
|
|
|
else
|
|
|
|
return &pg_asciiformat;
|
|
|
|
}
|
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
/*
|
2008-05-10 05:31:58 +02:00
|
|
|
* Compute the byte distance to the end of the string or *target_width
|
2014-05-06 18:12:18 +02:00
|
|
|
* display character positions, whichever comes first. Update *target_width
|
2008-05-10 05:31:58 +02:00
|
|
|
* to be the number of display character positions actually filled.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
|
|
|
static int
|
|
|
|
strlen_max_width(unsigned char *str, int *target_width, int encoding)
|
|
|
|
{
|
|
|
|
unsigned char *start = str;
|
2008-05-10 05:31:58 +02:00
|
|
|
unsigned char *end = str + strlen((char *) str);
|
2009-06-11 16:49:15 +02:00
|
|
|
int curr_width = 0;
|
2008-05-08 19:04:26 +02:00
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
while (str < end)
|
2008-05-08 19:04:26 +02:00
|
|
|
{
|
2009-06-11 16:49:15 +02:00
|
|
|
int char_width = PQdsplen((char *) str, encoding);
|
2008-05-08 19:04:26 +02:00
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* If the display width of the new character causes the string to
|
|
|
|
* exceed its target width, skip it and return. However, if this is
|
|
|
|
* the first character of the string (curr_width == 0), we have to
|
|
|
|
* accept it.
|
2008-05-08 19:04:26 +02:00
|
|
|
*/
|
2008-05-10 05:31:58 +02:00
|
|
|
if (*target_width < curr_width + char_width && curr_width != 0)
|
2008-05-08 19:04:26 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
curr_width += char_width;
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-10 05:31:58 +02:00
|
|
|
str += PQmblen((char *) str, encoding);
|
2008-05-08 19:04:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
*target_width = curr_width;
|
2009-06-11 16:49:15 +02:00
|
|
|
|
2008-05-08 19:04:26 +02:00
|
|
|
return str - start;
|
|
|
|
}
|