output: Add support for captions.
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 16 Sep 2014 03:36:05 +0000 (20:36 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 16 Sep 2014 03:36:05 +0000 (20:36 -0700)
This brings the output format a small step closer to the SPSS output model,
which has separate title and caption for tables.

15 files changed:
doc/invoking.texi
src/language/data-io/list.c
src/language/dictionary/sys-file-info.c
src/output/ascii.c
src/output/cairo.c
src/output/csv.c
src/output/html.c
src/output/odt.c
src/output/render.c
src/output/tab.c
src/output/tab.h
src/output/table-item.c
src/output/table-item.h
src/output/table.c
tests/output/render-test.c

index 16dc1371b162ebc9940e96300d4ead2d5cbd44b0..ea839fb1feca9c7020947b7a9a54161cf5630c0a 100644 (file)
@@ -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
index e03bd08c126c903b5092a7c8aa1009d674613b3a..153c204f219c780ec3b94ef92dbb18146aee20d8 100644 (file)
@@ -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;
index b9e0a85c48c27b972529069d40c7861a055d14e4..bb57825e62f551065856ade2e9682b956a471319 100644 (file)
@@ -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));
 }
 \f
 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);
index 562cd72ea76b75df68b0d352bf39c6bf6f28e90e..0b6d659fa8c7317f31329ced1b60bd25abdb98a6 100644 (file)
@@ -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);
 }
index fa9b2e18725e89c5f8c6edd9ccf9d7e6ffda8495..5197ffee9aa24bc9d274a6fb805943f202eae528 100644 (file)
@@ -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);
 
index 2784b24998757649e96c7f863f709753873abd6c..cf175458716b29b952f4de58c2546bce0a176870 100644 (file)
@@ -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;
index 715682a9ea02087a92f4857938a4106b9ead7da3..9e7f567a27274866efda6d67fddef2ca74082d3c 100644 (file)
@@ -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, "<TFOOT><TR><TD COLSPAN=%d>", table_nc (t));
+      *tfoot = true;
+    }
+  else
+    fputs ("\n<BR>", 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 ("<TABLE>", html->file);
 
+  if (caption)
+    {
+      put_tfoot (html, t, &tfoot);
+      escape_string (html->file, caption, strlen (caption), " ", "<BR>");
+    }
   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, "<TFOOT><TR><TD COLSPAN=%d>",
-                             table_nc (t));
-                  else
-                    fputs ("\n<BR>", html->file);
+                  put_tfoot (html, t, &tfoot);
                   str_format_26adic (++footnote_idx, false, marker, sizeof marker);
                   fprintf (html->file, "<SUP>%s</SUP> ", 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 ("</TD></TR></TFOOT>\n", html->file);
-      footnote_idx = 0;
-    }
+  if (tfoot)
+    fputs ("</TD></TR></TFOOT>\n", html->file);
+  footnote_idx = 0;
 
   fputs ("<TBODY VALIGN=\"TOP\">\n", html->file);
 
index 21aaeb6c1a723ebe9927a0d02beb85fc67456c24..a29c045d0b67e8920344a0e3e0b6f6a5f99f4db8 100644 (file)
@@ -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
index 3ac603527e3a33fe73e3b72e068ac84c2eeba0a6..d282a8fce68797e0430fc7a95cf86a5c1dbe35dd 100644 (file)
@@ -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);
 
index 8ef077e38fae99f8a8b2d8c54b4c3b22f541e296..db384bdfc93408b434b08211ac774c03f60251a4 100644 (file)
@@ -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));
 }
 \f
 /* 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);
 }
 
index bb932fddcbfee60133936ff8bc45cacbb7a1cfa5..9371d812c652292de4fb8e64032665be96a12ab6 100644 (file)
@@ -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. */
index 45add25f346635e8ebc4ae67f94471876b11179d..c9fd0a62a75f8c275de8b46d13ad68389419b5e1 100644 (file)
 #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);
 }
index 15f5c568f37b15350363dc40a0fd29692575f2e1..855324416fa066cd73d6c03896567071e4279d7a 100644 (file)
 /* 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 *);
 \f
 /* This boilerplate for table_item, a subclass of output_item, was
    autogenerated by mk-class-boilerplate. */
index b7afb8f81f24fe4542bedbd6f9e484302de1f3cf..0667c067350ac0730a97a650ffe4e0d35f1d35df 100644 (file)
@@ -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.
index d051200c3cadba4f1f4c00a157755694701e45eb..0d6f9684f30a6f87457f06105b7519a4b4bd5155 100644 (file)
@@ -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
             {