Add support for reading and writing SPV files.
[pspp] / src / output / pivot-table.c
index 96821170eff043db82a47e88004d19f2ba84747c..30371cae368ad32884f51e675fab8b588e41a07e 100644 (file)
@@ -61,6 +61,70 @@ pivot_area_to_string (enum pivot_area area)
     }
 }
 
+void
+pivot_area_get_default_style (enum pivot_area area, struct area_style *style)
+{
+#define STYLE(BOLD, H, V, L, R, T, B) {                         \
+    .cell_style = {                                             \
+      .halign = TABLE_HALIGN_##H,                               \
+      .valign = TABLE_VALIGN_##V,                               \
+      .margin = { [TABLE_HORZ][0] = L, [TABLE_HORZ][1] = R,     \
+                  [TABLE_VERT][0] = T, [TABLE_VERT][1] = B },   \
+    },                                                          \
+    .font_style = {                                             \
+      .bold = BOLD,                                             \
+      .fg = { [0] = CELL_COLOR_BLACK, [1] = CELL_COLOR_BLACK},  \
+      .bg = { [0] = CELL_COLOR_WHITE, [1] = CELL_COLOR_WHITE},  \
+      .size = 9,                                                \
+    },                                                          \
+  }
+  static const struct 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),
+    [PIVOT_AREA_COLUMN_LABELS] = STYLE(false, CENTER, BOTTOM,  8,11,1,3),
+    [PIVOT_AREA_ROW_LABELS]    = STYLE(false, LEFT,   TOP,     8,11,1,3),
+    [PIVOT_AREA_DATA]          = STYLE(false, MIXED,  TOP,     8,11,1,1),
+    [PIVOT_AREA_LAYERS]        = STYLE(false, LEFT,   BOTTOM,  8,11,1,3),
+    };
+#undef STYLE
+
+  *style = default_area_styles[area];
+  style->font_style.typeface = xstrdup ("SansSerif");
+}
+
+void
+pivot_border_get_default_style (enum pivot_border border,
+                                struct table_border_style *style)
+{
+  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,
+  };
+  *style = (struct table_border_style) {
+      .stroke = default_strokes[border],
+      .color = CELL_COLOR_BLACK,
+  };
+}
+
 /* Returns the name of BORDER. */
 const char *
 pivot_border_to_string (enum pivot_border border)
@@ -279,7 +343,7 @@ pivot_dimension_create__ (struct pivot_table *table,
     axis->dimensions, (axis->n_dimensions + 1) * sizeof *axis->dimensions);
   axis->dimensions[axis->n_dimensions++] = d;
 
-  /* XXX extent and label_depth need to be calculated later. */
+  /* axis->extent and axis->label_depth will be calculated later. */
 
   return d;
 }
@@ -580,14 +644,6 @@ pivot_result_class_change (const char *s_, const struct fmt_spec *format)
   return rc != NULL;
 }
 \f
-/* One piece of data within a pivot table. */
-struct pivot_cell
-  {
-    struct hmap_node hmap_node; /* In struct pivot_table's 'cells' hmap. */
-    struct pivot_value *value;
-    unsigned int idx[];         /* One index per table dimension. */
-  };
-
 /* Pivot tables. */
 
 /* Creates and returns a new pivot table with the given TITLE.  TITLE should be
@@ -630,36 +686,17 @@ struct pivot_table *
 pivot_table_create__ (struct pivot_value *title)
 {
   struct pivot_table *table = xzalloc (sizeof *table);
+  table->ref_cnt = 1;
   table->weight_format = (struct fmt_spec) { FMT_F, 40, 0 };
   table->title = title;
 
-  /* Set default area styles. */
-#define STYLE(BOLD, H, V, L, R, T, B) {                         \
-    .cell_style = {                                             \
-      .halign = TABLE_HALIGN_##H,                               \
-      .valign = TABLE_VALIGN_##V,                               \
-      .margin = { [TABLE_HORZ][0] = L, [TABLE_HORZ][1] = R,     \
-                  [TABLE_VERT][0] = T, [TABLE_VERT][1] = B },   \
-    },                                                          \
-      .font_style = {                                           \
-      .bold = BOLD,                                             \
-      .fg = { [0] = CELL_COLOR_BLACK, [1] = CELL_COLOR_BLACK},  \
-      .bg = { [0] = CELL_COLOR_WHITE, [1] = CELL_COLOR_WHITE},  \
-    },                                                          \
-         }
-  static const struct 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),
-    [PIVOT_AREA_COLUMN_LABELS] = STYLE(false, CENTER, BOTTOM,  8,11,1,3),
-    [PIVOT_AREA_ROW_LABELS]    = STYLE(false, LEFT,   TOP,     8,11,1,3),
-    [PIVOT_AREA_DATA]          = STYLE(false, MIXED,  TOP,     8,11,1,1),
-    [PIVOT_AREA_LAYERS]        = STYLE(false, LEFT,   BOTTOM,  8,11,1,3),
-    };
-#undef STYLE
+  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;
+
   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
-    table->areas[i] = default_area_styles[i];
+    pivot_area_get_default_style (i, &table->areas[i]);
 
   /* Set default border styles. */
   static const enum table_stroke default_strokes[PIVOT_N_BORDERS] = {
@@ -713,12 +750,27 @@ pivot_table_create_for_text (struct pivot_value *title,
   return table;
 }
 
-/* Destroys TABLE and frees everything it points to. */
+/* Increases TABLE's reference count, indicating that it has an additional
+   owner.  A pivot table that is shared among multiple owners must not be
+   modified. */
+struct pivot_table *
+pivot_table_ref (const struct pivot_table *table_)
+{
+  struct pivot_table *table = CONST_CAST (struct pivot_table *, table_);
+  table->ref_cnt++;
+  return table;
+}
+
+/* Decreases TABLE's reference count, indicating that it has one fewer owner.
+   If TABLE no longer has any owners, it is freed. */
 void
-pivot_table_destroy (struct pivot_table *table)
+pivot_table_unref (struct pivot_table *table)
 {
   if (!table)
     return;
+  assert (table->ref_cnt > 0);
+  if (--table->ref_cnt)
+    return;
 
   free (table->current_layer);
   free (table->table_look);
@@ -771,6 +823,14 @@ pivot_table_destroy (struct pivot_table *table)
   free (table);
 }
 
+/* Returns true if TABLE has more than one owner.  A pivot table that is shared
+   among multiple owners must not be modified. */
+bool
+pivot_table_is_shared (const struct pivot_table *table)
+{
+  return table->ref_cnt > 1;
+}
+
 /* 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.
@@ -957,9 +1017,9 @@ pivot_make_default_footnote_marker (int idx, bool show_numeric_markers)
   return pivot_value_new_user_text (text, -1);
 }
 
-/* Creates or modifies a footnote in TABLE with 0-based number IDX.  If MARKER
-   is nonnull, sets the footnote's marker; if CONTENT is nonnull, sets the
-   footnote's content. */
+/* Creates or modifies a footnote in TABLE with 0-based number IDX (and creates
+   all lower indexes as a side effect).  If MARKER is nonnull, sets the
+   footnote's marker; if CONTENT is nonnull, sets the footnote's content. */
 struct pivot_footnote *
 pivot_table_create_footnote__ (struct pivot_table *table, size_t idx,
                                struct pivot_value *marker,
@@ -1344,6 +1404,52 @@ compose_headings (const struct pivot_axis *axis,
   return headings;
 }
 
+static void
+free_headings (const struct pivot_axis *axis, char ***headings)
+{
+  for (size_t i = 0; i < axis->label_depth; i++)
+    {
+      for (size_t j = 0; j < axis->extent; j++)
+        free (headings[i][j]);
+      free (headings[i]);
+    }
+  free (headings);
+}
+
+static void
+pivot_table_sizing_dump (const char *name, const struct pivot_table_sizing *s,
+                         int indentation)
+{
+  indent (indentation);
+  printf ("%ss: min=%d, max=%d\n", name, s->range[0], s->range[1]);
+  if (s->n_widths)
+    {
+      indent (indentation + 1);
+      printf ("%s widths:", name);
+      for (size_t i = 0; i < s->n_widths; i++)
+        printf (" %d", s->widths[i]);
+      printf ("\n");
+    }
+  if (s->n_breaks)
+    {
+      indent (indentation + 1);
+      printf ("break after %ss:", name);
+      for (size_t i = 0; i < s->n_breaks; i++)
+        printf (" %zu", s->breaks[i]);
+      printf ("\n");
+    }
+  if (s->n_keeps)
+    {
+      indent (indentation + 1);
+      printf ("keep %ss together:", name);
+      for (size_t i = 0; i < s->n_keeps; i++)
+        printf (" [%zu,%zu]",
+                s->keeps[i].ofs,
+                s->keeps[i].ofs + s->keeps[i].n - 1);
+      printf ("\n");
+    }
+}
+
 void
 pivot_table_dump (const struct pivot_table *table, int indentation)
 {
@@ -1363,9 +1469,17 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
   if (table->date)
     {
       indent (indentation);
-      printf ("date: %s", ctime (&table->date)); /* XXX thread unsafe */
+      char buf[26];
+      printf ("date: %s", ctime_r (&table->date, buf));
     }
 
+  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);
+
   indent (indentation);
   printf ("areas:\n");
   for (enum pivot_area area = 0; area < PIVOT_N_AREAS; area++)
@@ -1422,7 +1536,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
           fputs (" =", stdout);
 
           struct pivot_value **names = xnmalloc (layer_axis->label_depth,
-                                               sizeof *names);
+                                                 sizeof *names);
           size_t n_names = 0;
           for (const struct pivot_category *c
                  = d->presentation_leaves[layer_indexes[i]];
@@ -1462,6 +1576,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
             }
           putchar ('\n');
         }
+      free_headings (&table->axes[PIVOT_AXIS_COLUMN], column_headings);
 
       indent (indentation + 1);
       printf ("-----------------------------------------------\n");
@@ -1510,6 +1625,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
 
       free (column_enumeration);
       free (row_enumeration);
+      free_headings (&table->axes[PIVOT_AXIS_ROW], row_headings);
     }
 
   pivot_table_dump_value (table->caption, "caption", indentation);
@@ -1528,6 +1644,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
       putchar ('\n');
     }
 
+  free (dindexes);
   settings_set_decimal_char (old_decimal);
 }
 \f
@@ -1735,7 +1852,7 @@ pivot_value_format_body (const struct pivot_value *value,
       break;
 
     case PIVOT_VALUE_TEMPLATE:
-      pivot_format_template (out, value->template.s, value->template.args,
+      pivot_format_template (out, value->template.local, value->template.args,
                              value->template.n_args, show_values,
                              show_variables);
       break;
@@ -1794,6 +1911,7 @@ 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);
 
       switch (value->type)
         {
@@ -1823,7 +1941,9 @@ pivot_value_destroy (struct pivot_value *value)
           break;
 
         case PIVOT_VALUE_TEMPLATE:
-          free (value->template.s);
+          free (value->template.local);
+          if (value->template.id != value->template.local)
+            free (value->template.id);
           for (size_t i = 0; i < value->template.n_args; i++)
             pivot_argument_uninit (&value->template.args[i]);
           free (value->template.args);
@@ -1837,15 +1957,16 @@ 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,
+                       const struct font_style *base_font_style,
+                       const struct cell_style *base_cell_style,
                        struct area_style *area)
 {
   font_style_copy (&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. */
@@ -2058,6 +2179,12 @@ void
 pivot_value_add_footnote (struct pivot_value *v,
                           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;