From 8dd405d0900f29f69e54726a79045c119146de89 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 15 Sep 2014 20:36:05 -0700 Subject: [PATCH] output: Add support for captions. This brings the output format a small step closer to the SPSS output model, which has separate title and caption for tables. --- doc/invoking.texi | 28 ++++++++++++++++++--- src/language/data-io/list.c | 4 +-- src/language/dictionary/sys-file-info.c | 30 +++++++++------------- src/output/ascii.c | 3 ++- src/output/cairo.c | 6 +++-- src/output/csv.c | 16 ++++++++++++ src/output/html.c | 33 +++++++++++++++++-------- src/output/odt.c | 13 ++++++++++ src/output/render.c | 26 ++++++++++++++----- src/output/tab.c | 18 +++++++++++++- src/output/tab.h | 12 ++++----- src/output/table-item.c | 29 +++++++++++++++++++--- src/output/table-item.h | 13 +++++++--- src/output/table.c | 2 +- tests/output/render-test.c | 4 +-- 15 files changed, 177 insertions(+), 60 deletions(-) diff --git a/doc/invoking.texi b/doc/invoking.texi index 16dc1371b1..ea839fb1fe 100644 --- a/doc/invoking.texi +++ b/doc/invoking.texi @@ -422,7 +422,12 @@ only the first character is used; if @var{qualifier} is the empty string, then fields are never quoted. @item @option{-O titles=@var{boolean}} -Whether table titles should be printed. Default: @code{on}. +Whether table titles (brief descriptions) should be printed. Default: +@code{on}. + +@item @option{-O captions=@var{boolean}} +Whether table captions (more extensive descriptions) should be +printed. Default: on. @end table The CSV format used is an extension to that specified in RFC 4180: @@ -432,9 +437,24 @@ The CSV format used is an extension to that specified in RFC 4180: Each table row is output on a separate line, and each column is output as a field. The contents of a cell that spans multiple rows or columns is output only for the top-left row and column; the rest are -output as empty fields. When a table has a caption and captions are -enabled, the caption is output just above the table as a single field -prefixed by @samp{Table:}. +output as empty fields. + +@item Titles +When a table has a title and titles are enabled, the title is output +just above the table as a single field prefixed by @samp{Table:}. + +@item Captions +When a table has a caption and captions are enabled, the caption is +output just below the table as a single field prefixed by +@samp{Caption:}. + +@item Footnotes +Within a table, footnote markers are output as bracketed letters +following the cell's contents, e.g.@tie{}@samp{[a]}, @samp{[b]}, +@enddots{} The footnotes themselves are output following the body of +the table, as a separate two-column table introduced with a line that +says @samp{Footnotes:}. Each row in the table represent one footnote: +the first column is the marker, the second column is the text. @item Text Text in output is printed as a field on a line by itself. The TITLE diff --git a/src/language/data-io/list.c b/src/language/data-io/list.c index e03bd08c12..153c204f21 100644 --- a/src/language/data-io/list.c +++ b/src/language/data-io/list.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 1997-9, 2000, 2006, 2009-2011, 2013 Free Software Foundation, Inc. + Copyright (C) 1997-9, 2000, 2006, 2009-2011, 2013, 2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -128,7 +128,7 @@ list_execute (const struct lst_cmd *lcmd, struct dataset *ds) casereader_destroy (group); - table_item_submit (table_item_create (t, "Data List")); + table_item_submit (table_item_create (t, "Data List", NULL)); } ok = casegrouper_destroy (grouper); ok = proc_commit (ds) && ok; diff --git a/src/language/dictionary/sys-file-info.c b/src/language/dictionary/sys-file-info.c index b9e0a85c48..bb57825e62 100644 --- a/src/language/dictionary/sys-file-info.c +++ b/src/language/dictionary/sys-file-info.c @@ -232,7 +232,7 @@ cmd_sysfile_info (struct lexer *lexer, struct dataset *ds UNUSED) describe_variable (dict_get_var (d, i), DF_ALL & ~DF_AT_ATTRIBUTES)); - table_item_submit (table_item_create (table, NULL /* XXX */)); + table_item_submit (table_item_create (table, NULL /* XXX */, NULL)); dict_destroy (d); @@ -443,7 +443,7 @@ display_variables (const struct variable **vl, size_t n, int flags) if (flags & ~DF_DICT_INDEX) tab_vline (t, TAL_1, nc - 1, 0, r - 1); #endif - table_item_submit (table_item_create (table, NULL /* XXX */)); + table_item_submit (table_item_create (table, NULL /* XXX */, NULL)); } static bool @@ -516,7 +516,8 @@ display_data_file_attributes (struct attrset *set, int flags) { if (count_attributes (set, flags)) table_item_submit (table_item_create (describe_attributes (set, flags), - _("Custom data file attributes."))); + _("Custom data file attributes."), + NULL)); } static struct table * @@ -953,7 +954,6 @@ report_encodings (const struct file_handle *h, const struct sfm_reader *r) size_t n_encodings, n_strings, n_unique_strings; size_t i, j; struct tab_table *t; - struct text_item *text; struct pool *pool; size_t row; @@ -998,16 +998,12 @@ report_encodings (const struct file_handle *h, const struct sfm_reader *r) return; } - text = text_item_create_format ( - TEXT_ITEM_PARAGRAPH, - _("The following table lists the encodings that can successfully read %s, " - "by specifying the encoding name on the GET command's ENCODING " - "subcommand. Encodings that yield identical text are listed " - "together."), fh_get_name (h)); - text_item_submit (text); - t = tab_create (2, n_encodings + 1); tab_title (t, _("Usable encodings for %s."), fh_get_name (h)); + tab_caption (t, _("Encodings that can successfully read %s (by specifying " + "the encoding name on the GET command's ENCODING " + "subcommand). Encodings that yield identical text are " + "listed together."), fh_get_name (h)); tab_headers (t, 1, 0, 1, 0); tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 1, n_encodings); tab_hline (t, TAL_1, 0, 1, 1); @@ -1039,15 +1035,11 @@ report_encodings (const struct file_handle *h, const struct sfm_reader *r) return; } - text = text_item_create_format ( - TEXT_ITEM_PARAGRAPH, - _("The following table lists text strings in the file dictionary that " - "the encodings above interpret differently, along with those " - "interpretations.")); - text_item_submit (text); - t = tab_create (3, (n_encodings * n_unique_strings) + 1); tab_title (t, _("%s encoded text strings."), fh_get_name (h)); + tab_caption (t, _("Text strings in the file dictionary that the previously " + "listed encodings interpret differently, along with the " + "interpretations.")); tab_headers (t, 1, 0, 1, 0); tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 2, n_encodings * n_unique_strings); tab_hline (t, TAL_1, 0, 2, 1); diff --git a/src/output/ascii.c b/src/output/ascii.c index 562cd72ea7..0b6d659fa8 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -464,7 +464,8 @@ ascii_output_text (struct ascii_driver *a, const char *text) { struct table_item *table_item; - table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL); + table_item = table_item_create (table_from_string (TAB_LEFT, text), + NULL, NULL); ascii_output_table_item (a, table_item); table_item_unref (table_item); } diff --git a/src/output/cairo.c b/src/output/cairo.c index fa9b2e1872..5197ffee9a 100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@ -1296,7 +1296,8 @@ xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr) struct table_item *table_item; struct xr_rendering *r; - table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL); + table_item = table_item_create (table_from_string (TAB_LEFT, text), + NULL, NULL); r = xr_rendering_create (xr, &table_item->output_item, cr); table_item_unref (table_item); @@ -1600,7 +1601,8 @@ xr_create_text_renderer (struct xr_driver *xr, const char *text) struct table_item *table_item; struct xr_render_fsm *fsm; - table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL); + table_item = table_item_create (table_from_string (TAB_LEFT, text), + NULL, NULL); fsm = xr_render_table (xr, table_item); table_item_unref (table_item); diff --git a/src/output/csv.c b/src/output/csv.c index 2784b24998..cf17545871 100644 --- a/src/output/csv.c +++ b/src/output/csv.c @@ -47,6 +47,7 @@ struct csv_driver int quote; /* Quote character (usually ' or ") or 0. */ char *quote_set; /* Characters that force quoting. */ bool titles; /* Print table titles? */ + bool captions; /* Print table captions? */ char *file_name; /* Output file name. */ char *command_name; /* Current command. */ @@ -88,6 +89,7 @@ csv_create (const char *file_name, enum settings_output_devices device_type, free (quote); csv->quote_set = xasprintf ("\n\r\t%s%c", csv->separator, csv->quote); csv->titles = parse_boolean (opt (d, o, "titles", "true")); + csv->captions = parse_boolean (opt (d, o, "captions", "true")); csv->file_name = xstrdup (file_name); csv->file = fn_open (csv->file_name, "w"); csv->n_items = 0; @@ -190,6 +192,7 @@ csv_output_subtable (struct csv_driver *csv, struct string *s, { const struct table *t = table_item_get_table (item); const char *title = table_item_get_title (item); + const char *caption = table_item_get_caption (item); int y, x; if (csv->titles && title != NULL) @@ -239,6 +242,12 @@ csv_output_subtable (struct csv_driver *csv, struct string *s, table_cell_free (&cell); } } + + if (csv->captions && caption != NULL) + { + csv_output_field_format (csv, "Caption: %s", caption); + putc ('\n', csv->file); + } } static void @@ -260,6 +269,7 @@ csv_submit (struct output_driver *driver, { struct table_item *table_item = to_table_item (output_item); const char *title = table_item_get_title (table_item); + const char *caption = table_item_get_caption (table_item); const struct table *t = table_item_get_table (table_item); int footnote_idx; int x, y; @@ -327,6 +337,12 @@ csv_submit (struct output_driver *driver, putc ('\n', csv->file); } + if (csv->captions && caption != NULL) + { + csv_output_field_format (csv, "Caption: %s", caption); + putc ('\n', csv->file); + } + if (footnote_idx) { size_t i; diff --git a/src/output/html.c b/src/output/html.c index 715682a9ea..9e7f567a27 100644 --- a/src/output/html.c +++ b/src/output/html.c @@ -373,16 +373,35 @@ put_border (FILE *file, int n_borders, int style, const char *border_name) style == TAL_1 ? "thin solid" : "double"); } +static void +put_tfoot (struct html_driver *html, const struct table *t, bool *tfoot) +{ + if (!*tfoot) + { + fprintf (html->file, "", table_nc (t)); + *tfoot = true; + } + else + fputs ("\n
", html->file); +} + static void html_output_table (struct html_driver *html, const struct table_item *item) { const struct table *t = table_item_get_table (item); const char *title = table_item_get_title (item); + const char *caption = table_item_get_caption (item); int footnote_idx = 0; + bool tfoot = false; int y; fputs ("", html->file); + if (caption) + { + put_tfoot (html, t, &tfoot); + escape_string (html->file, caption, strlen (caption), " ", "
"); + } footnote_idx = 0; for (y = 0; y < table_nr (t); y++) { @@ -405,11 +424,7 @@ html_output_table (struct html_driver *html, const struct table_item *item) { char marker[16]; - if (!footnote_idx) - fprintf (html->file, "\n", html->file); - footnote_idx = 0; - } + if (tfoot) + fputs ("\n", html->file); + footnote_idx = 0; fputs ("\n", html->file); diff --git a/src/output/odt.c b/src/output/odt.c index 21aaeb6c1a..a29c045d0b 100644 --- a/src/output/odt.c +++ b/src/output/odt.c @@ -454,6 +454,7 @@ static void write_table (struct odt_driver *odt, const struct table_item *item) { const struct table *tab = table_item_get_table (item); + const char *caption = table_item_get_caption (item); const char *title = table_item_get_title (item); int r, c; @@ -560,6 +561,18 @@ write_table (struct odt_driver *odt, const struct table_item *item) } xmlTextWriterEndElement (odt->content_wtr); /* table */ + + /* Write a caption for the table */ + if (caption != NULL) + { + xmlTextWriterStartElement (odt->content_wtr, _xml("text:h")); + xmlTextWriterWriteFormatAttribute (odt->content_wtr, + _xml("text:outline-level"), "%d", 2); + xmlTextWriterWriteString (odt->content_wtr, + _xml (table_item_get_caption (item)) ); + xmlTextWriterEndElement (odt->content_wtr); + } + } static void diff --git a/src/output/render.c b/src/output/render.c index 3ac603527e..d282a8fce6 100644 --- a/src/output/render.c +++ b/src/output/render.c @@ -1416,12 +1416,15 @@ struct render_pager struct render_break y_break; }; -static void +static const struct render_page * render_pager_add_table (struct render_pager *p, struct table *table) { + struct render_page *page; + if (p->n_pages >= p->allocated_pages) p->pages = x2nrealloc (p->pages, &p->allocated_pages, sizeof *p->pages); - p->pages[p->n_pages++] = render_page_create (p->params, table); + page = p->pages[p->n_pages++] = render_page_create (p->params, table); + return page; } static void @@ -1483,17 +1486,28 @@ struct render_pager * render_pager_create (const struct render_params *params, const struct table_item *table_item) { + const char *caption = table_item_get_caption (table_item); + const char *title = table_item_get_title (table_item); + const struct render_page *body_page; struct render_pager *p; - const char *title; p = xzalloc (sizeof *p); p->params = params; - title = table_item_get_title (table_item); + /* Title. */ if (title) render_pager_add_table (p, table_from_string (TAB_LEFT, title)); - render_pager_add_table (p, table_ref (table_item_get_table (table_item))); - add_footnote_page (p, p->pages[p->n_pages - 1]); + + /* Body. */ + body_page = render_pager_add_table (p, table_ref (table_item_get_table ( + table_item))); + + /* Caption. */ + if (caption) + render_pager_add_table (p, table_from_string (TAB_LEFT, caption)); + + /* Footnotes. */ + add_footnote_page (p, body_page); render_pager_start_page (p); diff --git a/src/output/tab.c b/src/output/tab.c index 8ef077e38f..db384bdfc9 100644 --- a/src/output/tab.c +++ b/src/output/tab.c @@ -88,6 +88,7 @@ tab_create (int nc, int nr) table_set_nr (&t->table, nr); t->title = NULL; + t->caption = NULL; t->cf = nc; t->cc = pool_calloc (t->container, nr * nc, sizeof *t->cc); t->ct = pool_malloc (t->container, nr * nc); @@ -682,11 +683,24 @@ tab_title (struct tab_table *t, const char *title, ...) va_end (args); } +/* Set the caption of table T to CAPTION, which is formatted as if + passed to printf(). */ +void +tab_caption (struct tab_table *t, const char *caption, ...) +{ + va_list args; + + free (t->caption); + va_start (args, caption); + t->caption = xvasprintf (caption, args); + va_end (args); +} + /* Easy, type-safe way to submit a tab table to som. */ void tab_submit (struct tab_table *t) { - table_item_submit (table_item_create (&t->table, t->title)); + table_item_submit (table_item_create (&t->table, t->title, t->caption)); } /* Editing. */ @@ -770,6 +784,8 @@ tab_destroy (struct table *table) struct tab_table *t = tab_cast (table); free (t->title); t->title = NULL; + free (t->caption); + t->caption = NULL; pool_destroy (t->container); } diff --git a/src/output/tab.h b/src/output/tab.h index bb932fddcb..9371d812c6 100644 --- a/src/output/tab.h +++ b/src/output/tab.h @@ -25,8 +25,8 @@ Some of the features of this type of table are obsolete but have not yet been removed, because some code still uses them. These features are: - - The title. The title is a property of the table_item (see - output/table-item.h) in which a table is embedded, not a property of + - The title and caption. These are properties of the table_item (see + output/table-item.h) in which a table is embedded, not properties of the table itself. - Row and columns offsets (via tab_offset(), tab_next_row()). This @@ -57,10 +57,8 @@ struct tab_table struct table table; struct pool *container; - /* Table title, or a null pointer if no title has been set. - - The table title is properly part of struct table_item, not struc*/ - char *title; + /* Table title and caption, or null. */ + char *title, *caption; int cf; /* Column factor for indexing purposes. */ /* Table contents. @@ -107,6 +105,8 @@ void tab_realloc (struct tab_table *, int nc, int nr); void tab_headers (struct tab_table *, int l, int r, int t, int b); void tab_title (struct tab_table *, const char *, ...) PRINTF_FORMAT (2, 3); +void tab_caption (struct tab_table *, const char *, ...) + PRINTF_FORMAT (2, 3); void tab_submit (struct tab_table *); /* Rules. */ diff --git a/src/output/table-item.c b/src/output/table-item.c index 45add25f34..c9fd0a62a7 100644 --- a/src/output/table-item.c +++ b/src/output/table-item.c @@ -29,15 +29,16 @@ #include "gl/xalloc.h" /* Initializes ITEM as a table item for rendering TABLE. The new table item - initially has the specified TITLE, which may be NULL if no title is yet - available. The caller retains ownership of TITLE. */ + initially has the specified TITLE and CAPTION, which may each be NULL. The + caller retains ownership of TITLE and CAPTION. */ struct table_item * -table_item_create (struct table *table, const char *title) +table_item_create (struct table *table, const char *title, const char *caption) { struct table_item *item = xmalloc (sizeof *item); output_item_init (&item->output_item, &table_item_class); item->table = table; item->title = title != NULL ? xstrdup (title) : NULL; + item->caption = caption != NULL ? xstrdup (caption) : NULL; return item; } @@ -69,6 +70,27 @@ table_item_set_title (struct table_item *item, const char *title) item->title = title != NULL ? xstrdup (title) : NULL; } +/* Returns ITEM's caption, which is a null pointer if no caption has been + set. */ +const char * +table_item_get_caption (const struct table_item *item) +{ + return item->caption; +} + +/* Sets ITEM's caption to CAPTION, replacing any previous caption. Specify + NULL for CAPTION to clear any caption from ITEM. The caller retains + ownership of CAPTION. + + This function may only be used on a table_item that is unshared. */ +void +table_item_set_caption (struct table_item *item, const char *caption) +{ + assert (!table_item_is_shared (item)); + free (item->caption); + item->caption = caption != NULL ? xstrdup (caption) : NULL; +} + /* Submits TABLE_ITEM to the configured output drivers, and transfers ownership to the output subsystem. */ void @@ -82,6 +104,7 @@ table_item_destroy (struct output_item *output_item) { struct table_item *item = to_table_item (output_item); free (item->title); + free (item->caption); table_unref (item->table); free (item); } diff --git a/src/output/table-item.h b/src/output/table-item.h index 15f5c568f3..855324416f 100644 --- a/src/output/table-item.h +++ b/src/output/table-item.h @@ -20,8 +20,10 @@ /* Table items. A table item is a subclass of an output item (see output-item.h) that - contains a table (see table.h) and some formatting properties (currently - just a title). */ + contains a table (see table.h) and some formatting properties. Currently + the formatting properties are an optional title (a brief description + typically displayed above the table) and an optional caption (a more verbose + description typically displayed below the table). */ #include "libpspp/compiler.h" #include "output/output-item.h" @@ -35,14 +37,19 @@ struct table_item struct output_item output_item; /* Superclass. */ struct table *table; /* The table to be rendered. */ char *title; /* May be null if there is no title. */ + char *caption; /* May be null if there is no caption. */ }; -struct table_item *table_item_create (struct table *, const char *title); +struct table_item *table_item_create (struct table *, const char *title, + const char *caption); const struct table *table_item_get_table (const struct table_item *); const char *table_item_get_title (const struct table_item *); void table_item_set_title (struct table_item *, const char *); + +const char *table_item_get_caption (const struct table_item *); +void table_item_set_caption (struct table_item *, const char *); /* This boilerplate for table_item, a subclass of output_item, was autogenerated by mk-class-boilerplate. */ diff --git a/src/output/table.c b/src/output/table.c index b7afb8f81f..0667c06735 100644 --- a/src/output/table.c +++ b/src/output/table.c @@ -356,7 +356,7 @@ static const struct table_class table_nested_class; struct table * table_create_nested (struct table *inner) { - return table_create_nested_item (table_item_create (inner, NULL)); + return table_create_nested_item (table_item_create (inner, NULL, NULL)); } /* Creates and returns a table with a single cell that contains INNER. diff --git a/tests/output/render-test.c b/tests/output/render-test.c index d051200c3c..0d6f9684f3 100644 --- a/tests/output/render-test.c +++ b/tests/output/render-test.c @@ -115,7 +115,7 @@ main (int argc, char **argv) table = tables[n_tables - 1]; if (transpose) table = table_transpose (table); - table_item_submit (table_item_create (table, NULL)); + table_item_submit (table_item_create (table, NULL, NULL)); } else draw (input); @@ -475,7 +475,7 @@ read_table (FILE *stream, struct table **tables, size_t n_tables) error (1, 0, "unexpected subtable modifier \"%c\"", *text); } tab_subtable (tab, c, r, c + cs - 1, r + rs - 1, opt, - table_item_create (table, NULL)); + table_item_create (table, NULL, NULL)); } else { -- 2.30.2
", - table_nc (t)); - else - fputs ("\n
", html->file); + put_tfoot (html, t, &tfoot); str_format_26adic (++footnote_idx, false, marker, sizeof marker); fprintf (html->file, "%s ", marker); escape_string (html->file, c->footnotes[i], @@ -420,11 +435,9 @@ html_output_table (struct html_driver *html, const struct table_item *item) table_cell_free (&cell); } } - if (footnote_idx) - { - fputs ("