pivot-table: Change the default format to omit empty rows and columns.
[pspp] / src / output / pivot-table.c
index cce72ef1defabaafad9bf2af6b60c25b3b9830bc..45704a7a1ed680afbdb18f40466ba452f2e3a6d4 100644 (file)
@@ -62,8 +62,8 @@ pivot_area_to_string (enum pivot_area area)
     }
 }
 
-void
-pivot_area_get_default_style (enum pivot_area area, struct area_style *style)
+const struct table_area_style *
+pivot_area_get_default_style (enum pivot_area area)
 {
 #define STYLE(BOLD, H, V, L, R, T, B) {                         \
     .cell_style = {                                             \
@@ -77,10 +77,11 @@ pivot_area_get_default_style (enum pivot_area area, struct area_style *style)
       .fg = { [0] = CELL_COLOR_BLACK, [1] = CELL_COLOR_BLACK},  \
       .bg = { [0] = CELL_COLOR_WHITE, [1] = CELL_COLOR_WHITE},  \
       .size = 9,                                                \
+      .typeface = (char *) "Sans Serif",                        \
     },                                                          \
   }
-  static const struct area_style default_area_styles[PIVOT_N_AREAS] = {
-    [PIVOT_AREA_TITLE]         = STYLE( true, CENTER, CENTER,  8,11,1,8),
+  static const struct table_area_style default_area_styles[PIVOT_N_AREAS] = {
+    [PIVOT_AREA_TITLE]         = STYLE(true, CENTER, CENTER,  8,11,1,8),
     [PIVOT_AREA_CAPTION]       = STYLE(false, LEFT,   TOP,     8,11,1,1),
     [PIVOT_AREA_FOOTER]        = STYLE(false, LEFT,   TOP,    11, 8,2,3),
     [PIVOT_AREA_CORNER]        = STYLE(false, LEFT,   BOTTOM,  8,11,1,1),
@@ -91,8 +92,7 @@ pivot_area_get_default_style (enum pivot_area area, struct area_style *style)
     };
 #undef STYLE
 
-  *style = default_area_styles[area];
-  style->font_style.typeface = xstrdup ("SansSerif");
+  return &default_area_styles[area];
 }
 
 void
@@ -193,6 +193,80 @@ pivot_table_sizing_uninit (struct pivot_table_sizing *sizing)
     }
 }
 \f
+/* Pivot table looks. */
+
+void
+pivot_table_look_init (struct pivot_table_look *look)
+{
+  memset (look, 0, sizeof *look);
+
+  look->omit_empty = true;
+  look->row_labels_in_corner = true;
+  look->width_ranges[TABLE_HORZ][0] = 36;
+  look->width_ranges[TABLE_HORZ][1] = 72;
+  look->width_ranges[TABLE_VERT][0] = 36;
+  look->width_ranges[TABLE_VERT][1] = 120;
+
+  for (size_t i = 0; i < PIVOT_N_AREAS; i++)
+    table_area_style_copy (NULL, &look->areas[i],
+                           pivot_area_get_default_style (i));
+
+  static const enum table_stroke default_strokes[PIVOT_N_BORDERS] = {
+    [PIVOT_BORDER_TITLE]        = TABLE_STROKE_NONE,
+    [PIVOT_BORDER_OUTER_LEFT]   = TABLE_STROKE_NONE,
+    [PIVOT_BORDER_OUTER_TOP]    = TABLE_STROKE_NONE,
+    [PIVOT_BORDER_OUTER_RIGHT]  = TABLE_STROKE_NONE,
+    [PIVOT_BORDER_OUTER_BOTTOM] = TABLE_STROKE_NONE,
+    [PIVOT_BORDER_INNER_LEFT]   = TABLE_STROKE_THICK,
+    [PIVOT_BORDER_INNER_TOP]    = TABLE_STROKE_THICK,
+    [PIVOT_BORDER_INNER_RIGHT]  = TABLE_STROKE_THICK,
+    [PIVOT_BORDER_INNER_BOTTOM] = TABLE_STROKE_THICK,
+    [PIVOT_BORDER_DATA_LEFT]    = TABLE_STROKE_THICK,
+    [PIVOT_BORDER_DATA_TOP]     = TABLE_STROKE_THICK,
+    [PIVOT_BORDER_DIM_ROW_HORZ] = TABLE_STROKE_SOLID,
+    [PIVOT_BORDER_DIM_ROW_VERT] = TABLE_STROKE_NONE,
+    [PIVOT_BORDER_DIM_COL_HORZ] = TABLE_STROKE_SOLID,
+    [PIVOT_BORDER_DIM_COL_VERT] = TABLE_STROKE_SOLID,
+    [PIVOT_BORDER_CAT_ROW_HORZ] = TABLE_STROKE_NONE,
+    [PIVOT_BORDER_CAT_ROW_VERT] = TABLE_STROKE_NONE,
+    [PIVOT_BORDER_CAT_COL_HORZ] = TABLE_STROKE_SOLID,
+    [PIVOT_BORDER_CAT_COL_VERT] = TABLE_STROKE_SOLID,
+  };
+  for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
+    look->borders[i] = (struct table_border_style) {
+      .stroke = default_strokes[i],
+      .color = CELL_COLOR_BLACK,
+    };
+}
+
+void
+pivot_table_look_uninit (struct pivot_table_look *look)
+{
+  free (look->name);
+
+  for (size_t i = 0; i < PIVOT_N_AREAS; i++)
+    table_area_style_uninit (&look->areas[i]);
+
+  free (look->continuation);
+}
+
+static char *
+xstrdup_if_nonempty (const char *s)
+{
+  return s && s[0] ? xstrdup (s) : NULL;
+}
+
+void
+pivot_table_look_copy (struct pivot_table_look *dst,
+                       const struct pivot_table_look *src)
+{
+  *dst = *src;
+  dst->name = xstrdup_if_nonempty (src->name);
+  for (size_t i = 0; i < PIVOT_N_AREAS; i++)
+    table_area_style_copy (NULL, &dst->areas[i], &src->areas[i]);
+  dst->continuation = xstrdup_if_nonempty (src->continuation);
+}
+\f
 /* Axes. */
 
 /* Returns the name of AXIS_TYPE. */
@@ -701,50 +775,14 @@ pivot_table_create__ (struct pivot_value *title, const char *subtype)
 {
   struct pivot_table *table = xzalloc (sizeof *table);
   table->ref_cnt = 1;
+  table->show_caption = true;
   table->weight_format = (struct fmt_spec) { FMT_F, 40, 0 };
   table->title = title;
-  table->subtype = pivot_value_new_text (subtype);
-
-  const char *command_id = output_get_command_name ();
-  table->command_c = command_id ? xstrdup (command_id) : NULL;
+  table->subtype = subtype ? pivot_value_new_text (subtype) : NULL;
+  table->command_c = output_get_command_name ();
 
-  table->sizing[TABLE_HORZ].range[0] = 50;
-  table->sizing[TABLE_HORZ].range[1] = 72;
-  table->sizing[TABLE_VERT].range[0] = 36;
-  table->sizing[TABLE_VERT].range[1] = 120;
+  pivot_table_look_init (&table->look);
 
-  for (size_t i = 0; i < PIVOT_N_AREAS; i++)
-    pivot_area_get_default_style (i, &table->areas[i]);
-
-  /* Set default border styles. */
-  static const enum table_stroke default_strokes[PIVOT_N_BORDERS] = {
-    [PIVOT_BORDER_TITLE]        = TABLE_STROKE_NONE,
-    [PIVOT_BORDER_OUTER_LEFT]   = TABLE_STROKE_NONE,
-    [PIVOT_BORDER_OUTER_TOP]    = TABLE_STROKE_NONE,
-    [PIVOT_BORDER_OUTER_RIGHT]  = TABLE_STROKE_NONE,
-    [PIVOT_BORDER_OUTER_BOTTOM] = TABLE_STROKE_NONE,
-    [PIVOT_BORDER_INNER_LEFT]   = TABLE_STROKE_THICK,
-    [PIVOT_BORDER_INNER_TOP]    = TABLE_STROKE_THICK,
-    [PIVOT_BORDER_INNER_RIGHT]  = TABLE_STROKE_THICK,
-    [PIVOT_BORDER_INNER_BOTTOM] = TABLE_STROKE_THICK,
-    [PIVOT_BORDER_DATA_LEFT]    = TABLE_STROKE_THICK,
-    [PIVOT_BORDER_DATA_TOP]     = TABLE_STROKE_THICK,
-    [PIVOT_BORDER_DIM_ROW_HORZ] = TABLE_STROKE_SOLID,
-    [PIVOT_BORDER_DIM_ROW_VERT] = TABLE_STROKE_NONE,
-    [PIVOT_BORDER_DIM_COL_HORZ] = TABLE_STROKE_SOLID,
-    [PIVOT_BORDER_DIM_COL_VERT] = TABLE_STROKE_SOLID,
-    [PIVOT_BORDER_CAT_ROW_HORZ] = TABLE_STROKE_NONE,
-    [PIVOT_BORDER_CAT_ROW_VERT] = TABLE_STROKE_NONE,
-    [PIVOT_BORDER_CAT_COL_HORZ] = TABLE_STROKE_SOLID,
-    [PIVOT_BORDER_CAT_COL_VERT] = TABLE_STROKE_SOLID,
-  };
-  for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
-    table->borders[i] = (struct table_border_style) {
-      .stroke = default_strokes[i],
-      .color = CELL_COLOR_BLACK,
-    };
-
-  table->row_labels_in_corner = true;
   hmap_init (&table->cells);
 
   return table;
@@ -793,13 +831,11 @@ pivot_table_unref (struct pivot_table *table)
     return;
 
   free (table->current_layer);
-  free (table->table_look);
+  pivot_table_look_uninit (&table->look);
 
   for (int i = 0; i < TABLE_N_AXES; i++)
     pivot_table_sizing_uninit (&table->sizing[i]);
 
-  free (table->continuation);
-
   for (int i = 0; i < sizeof table->ccs / sizeof *table->ccs; i++)
     free (table->ccs[i]);
 
@@ -820,9 +856,6 @@ pivot_table_unref (struct pivot_table *table)
   pivot_value_destroy (table->corner_text);
   pivot_value_destroy (table->caption);
 
-  for (size_t i = 0; i < PIVOT_N_AREAS; i++)
-    area_style_uninit (&table->areas[i]);
-
   for (size_t i = 0; i < table->n_dimensions; i++)
     pivot_dimension_destroy (table->dimensions[i]);
   free (table->dimensions);
@@ -851,6 +884,20 @@ pivot_table_is_shared (const struct pivot_table *table)
   return table->ref_cnt > 1;
 }
 
+const struct pivot_table_look *
+pivot_table_get_look (const struct pivot_table *table)
+{
+  return &table->look;
+}
+
+void
+pivot_table_set_look (struct pivot_table *table,
+                      const struct pivot_table_look *look)
+{
+  pivot_table_look_uninit (&table->look);
+  pivot_table_look_copy (&table->look, look);
+}
+
 /* Sets the format used for PIVOT_RC_COUNT cells to the one used for variable
    WV, which should be the weight variable for the dictionary whose data or
    statistics are being put into TABLE.
@@ -1056,8 +1103,9 @@ pivot_table_create_footnote__ (struct pivot_table *table, size_t idx,
           struct pivot_footnote *f = xmalloc (sizeof *f);
           f->idx = table->n_footnotes;
           f->marker = pivot_make_default_footnote_marker (
-            f->idx, table->show_numeric_markers);
+            f->idx, table->look.show_numeric_markers);
           f->content = NULL;
+          f->show = true;
 
           table->footnotes[table->n_footnotes++] = f;
         }
@@ -1138,7 +1186,7 @@ pivot_table_enumerate_axis (const struct pivot_table *table,
                                                 axis->n_dimensions), 1),
                                   sizeof *enumeration);
   size_t *p = enumeration;
-  size_t *dindexes = xcalloc (table->n_dimensions, sizeof *dindexes);
+  size_t *dindexes = XCALLOC (table->n_dimensions, size_t);
 
   size_t *axis_indexes;
   PIVOT_AXIS_FOR_EACH (axis_indexes, axis)
@@ -1168,6 +1216,14 @@ pivot_table_enumerate_axis (const struct pivot_table *table,
       memcpy (p, axis_indexes, axis->n_dimensions * sizeof *p);
       p += axis->n_dimensions;
     }
+  if (omit_empty && p == enumeration)
+    {
+      PIVOT_AXIS_FOR_EACH (axis_indexes, axis)
+        {
+          memcpy (p, axis_indexes, axis->n_dimensions * sizeof *p);
+          p += axis->n_dimensions;
+        }
+    }
   *p = SIZE_MAX;
   if (n)
     *n = (p - enumeration) / axis->n_dimensions;
@@ -1274,7 +1330,7 @@ pivot_table_assign_label_depth (struct pivot_table *table)
 {
   pivot_axis_assign_label_depth (table, PIVOT_AXIS_COLUMN, false);
   if (pivot_axis_assign_label_depth (
-        table, PIVOT_AXIS_ROW, (table->row_labels_in_corner
+        table, PIVOT_AXIS_ROW, (table->look.row_labels_in_corner
                                 && !table->corner_text))
       && table->axes[PIVOT_AXIS_COLUMN].label_depth == 0)
     table->axes[PIVOT_AXIS_COLUMN].label_depth = 1;
@@ -1355,8 +1411,8 @@ pivot_dimension_dump (const struct pivot_dimension *d, int indentation)
 }
 
 static void
-area_style_dump (enum pivot_area area, const struct area_style *a,
-                 int indentation)
+table_area_style_dump (enum pivot_area area, const struct table_area_style *a,
+                       int indentation)
 {
   indent (indentation);
   printf ("%s: ", pivot_area_to_string (area));
@@ -1435,11 +1491,13 @@ free_headings (const struct pivot_axis *axis, char ***headings)
 }
 
 static void
-pivot_table_sizing_dump (const char *name, const struct pivot_table_sizing *s,
+pivot_table_sizing_dump (const char *name,
+                         const int width_ranges[2],
+                         const struct pivot_table_sizing *s,
                          int indentation)
 {
   indent (indentation);
-  printf ("%ss: min=%d, max=%d\n", name, s->range[0], s->range[1]);
+  printf ("%ss: min=%d, max=%d\n", name, width_ranges[0], width_ranges[1]);
   if (s->n_widths)
     {
       indent (indentation + 1);
@@ -1483,36 +1541,40 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
   pivot_table_dump_string (table->dataset, "dataset", indentation);
   pivot_table_dump_string (table->datafile, "datafile", indentation);
   pivot_table_dump_string (table->notes, "notes", indentation);
-  pivot_table_dump_string (table->table_look, "table-look", indentation);
+  pivot_table_dump_string (table->look.name, "table-look", indentation);
   if (table->date)
     {
       indent (indentation);
-      char buf[26];
-      printf ("date: %s", ctime_r (&table->date, buf));
+
+      struct tm *tm = localtime (&table->date);
+      printf ("date: %d-%02d-%02d %d:%02d:%02d\n", tm->tm_year + 1900,
+              tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min,
+              tm->tm_sec);
     }
 
   indent (indentation);
   printf ("sizing:\n");
-  pivot_table_sizing_dump ("column", &table->sizing[TABLE_HORZ],
-                           indentation + 1);
-  pivot_table_sizing_dump ("row", &table->sizing[TABLE_VERT],
-                           indentation + 1);
+  pivot_table_sizing_dump ("column", table->look.width_ranges[TABLE_HORZ],
+                           &table->sizing[TABLE_HORZ], indentation + 1);
+  pivot_table_sizing_dump ("row", table->look.width_ranges[TABLE_VERT],
+                           &table->sizing[TABLE_VERT], indentation + 1);
 
   indent (indentation);
   printf ("areas:\n");
   for (enum pivot_area area = 0; area < PIVOT_N_AREAS; area++)
-    area_style_dump (area, &table->areas[area], indentation + 1);
+    table_area_style_dump (area, &table->look.areas[area], indentation + 1);
 
   indent (indentation);
   printf ("borders:\n");
   for (enum pivot_border border = 0; border < PIVOT_N_BORDERS; border++)
-    table_border_style_dump (border, &table->borders[border], indentation + 1);
+    table_border_style_dump (border, &table->look.borders[border],
+                             indentation + 1);
 
   for (size_t i = 0; i < table->n_dimensions; i++)
     pivot_dimension_dump (table->dimensions[i], indentation);
 
   /* Presentation and data indexes. */
-  size_t *dindexes = xcalloc (table->n_dimensions, sizeof *dindexes);
+  size_t *dindexes = XCALLOC (table->n_dimensions, size_t);
 
   const struct pivot_axis *layer_axis = &table->axes[PIVOT_AXIS_LAYER];
   if (layer_axis->n_dimensions)
@@ -1565,7 +1627,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
                 names[n_names++] = c->name;
             }
 
-          for (size_t i = n_names; i-- > 0; )
+          for (size_t i = n_names; i-- > 0;)
             {
               putchar (' ');
               pivot_value_dump (names[i]);
@@ -1575,9 +1637,9 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
       putchar ('\n');
 
       size_t *column_enumeration = pivot_table_enumerate_axis (
-        table, PIVOT_AXIS_COLUMN, layer_indexes, table->omit_empty, NULL);
+        table, PIVOT_AXIS_COLUMN, layer_indexes, table->look.omit_empty, NULL);
       size_t *row_enumeration = pivot_table_enumerate_axis (
-        table, PIVOT_AXIS_ROW, layer_indexes, table->omit_empty, NULL);
+        table, PIVOT_AXIS_ROW, layer_indexes, table->look.omit_empty, NULL);
 
       char ***column_headings = compose_headings (
         &table->axes[PIVOT_AXIS_COLUMN], column_enumeration,
@@ -1889,10 +1951,13 @@ pivot_value_format (const struct pivot_value *value,
                     enum settings_value_show show_variables,
                     struct string *out)
 {
-  pivot_value_format_body ( value, show_values, show_variables, out);
+  pivot_value_format_body (value, show_values, show_variables, out);
 
-  if (value->subscript)
-    ds_put_format (out, "_%s", value->subscript);
+  if (value->n_subscripts)
+    {
+      for (size_t i = 0; i < value->n_subscripts; i++)
+        ds_put_format (out, "%c%s", i ? ',' : '_', value->subscripts[i]);
+    }
 
   if (value->superscript)
     ds_put_format (out, "^%s", value->superscript);
@@ -1929,7 +1994,12 @@ pivot_value_destroy (struct pivot_value *value)
       /* Do not free the elements of footnotes because VALUE does not own
          them. */
       free (value->footnotes);
-      free (value->subscript);
+
+      for (size_t i = 0; i < value->n_subscripts; i++)
+        free (value->subscripts[i]);
+      free (value->subscripts);
+
+      free (value->superscript);
 
       switch (value->type)
         {
@@ -1975,21 +2045,22 @@ pivot_value_destroy (struct pivot_value *value)
    DEFAULT_STYLE for the parts of the style that VALUE doesn't override. */
 void
 pivot_value_get_style (struct pivot_value *value,
-                       const struct area_style *default_style,
-                       struct area_style *area)
+                       const struct font_style *base_font_style,
+                       const struct cell_style *base_cell_style,
+                       struct table_area_style *area)
 {
   font_style_copy (NULL, &area->font_style, (value->font_style
                                              ? value->font_style
-                                             : &default_style->font_style));
-  area->cell_style = (value->cell_style
-                      ? *value->cell_style
-                      : default_style->cell_style);
+                                             : base_font_style));
+  area->cell_style = *(value->cell_style
+                       ? value->cell_style
+                       : base_cell_style);
 }
 
 /* Copies AREA into VALUE's style. */
 void
 pivot_value_set_style (struct pivot_value *value,
-                       const struct area_style *area)
+                       const struct table_area_style *area)
 {
   if (value->font_style)
     font_style_uninit (value->font_style);
@@ -2108,12 +2179,6 @@ pivot_value_new_text_format (const char *format, ...)
   return value;
 }
 
-static char *
-xstrdup_if_nonempty (const char *s)
-{
-  return s && s[0] ? xstrdup (s) : NULL;
-}
-
 /* Returns a new pivot_value that represents X.
 
    The format to use for X is unspecified.  Usually the easiest way to specify
@@ -2218,6 +2283,12 @@ void
 pivot_value_add_footnote (struct pivot_value *v,
                           const struct pivot_footnote *footnote)
 {
+  /* Some legacy tables include numerous duplicate footnotes.  Suppress
+     them. */
+  for (size_t i = 0; i < v->n_footnotes; i++)
+    if (v->footnotes[i] == footnote)
+      return;
+
   v->footnotes = xrealloc (v->footnotes,
                            (v->n_footnotes + 1) * sizeof *v->footnotes);
   v->footnotes[v->n_footnotes++] = footnote;