pivot-table: New functions for setting captions, etc.
[pspp] / src / output / pivot-table.c
index 22cd20de1339b1103f545d0873b05a8b3243325f..a3325a1469324332ff947444559ad6da32bbafbf 100644 (file)
@@ -18,6 +18,9 @@
 
 #include "output/pivot-table.h"
 
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <math.h>
 #include <stdlib.h>
 
 #include "data/data-out.h"
@@ -45,8 +48,8 @@
 #define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 
-static const struct fmt_spec *pivot_table_get_format (
-  const struct pivot_table *, const char *s);
+static void pivot_table_use_rc (const struct pivot_table *, const char *s,
+                                struct fmt_spec *, bool *honor_small);
 \f
 /* Pivot table display styling. */
 
@@ -394,17 +397,19 @@ pivot_axis_iterator_next (size_t *indexes, const struct pivot_axis *axis)
 static void
 pivot_category_set_rc (struct pivot_category *category, const char *s)
 {
-  const struct fmt_spec *format = pivot_table_get_format (
-    category->dimension->table, s);
-  if (format)
-    category->format = *format;
+  if (!s)
+    return;
+
+  pivot_table_use_rc (category->dimension->table, s,
+                      &category->format, &category->honor_small);
 
   /* Ensure that the category itself, in addition to the cells within it, takes
      the format.  (It's kind of rare for a category to have a numeric format
      though.) */
   struct pivot_value *name = category->name;
   if (name->type == PIVOT_VALUE_NUMERIC && !name->numeric.format.w)
-    name->numeric.format = format ? *format : *settings_get_format ();
+    pivot_table_use_rc (category->dimension->table, s,
+                        &name->numeric.format, &name->numeric.honor_small);
 }
 
 static void
@@ -743,11 +748,11 @@ struct result_class
 /* Formats for most of the result classes. */
 static struct result_class result_classes[] =
   {
-    { PIVOT_RC_INTEGER,      { FMT_F,   40, 0 } },
-    { PIVOT_RC_PERCENT,      { FMT_PCT, 40, 1 } },
-    { PIVOT_RC_CORRELATION,  { FMT_F,   40, 3 } },
-    { PIVOT_RC_SIGNIFICANCE, { FMT_F,   40, 3 } },
-    { PIVOT_RC_RESIDUAL,     { FMT_F,   40, 2 } },
+    { PIVOT_RC_INTEGER,      { .type = FMT_F,   .w = 40, .d = 0 } },
+    { PIVOT_RC_PERCENT,      { .type = FMT_PCT, .w = 40, .d = 1 } },
+    { PIVOT_RC_CORRELATION,  { .type = FMT_F,   .w = 40, .d = 3 } },
+    { PIVOT_RC_SIGNIFICANCE, { .type = FMT_F,   .w = 40, .d = 3 } },
+    { PIVOT_RC_RESIDUAL,     { .type = FMT_F,   .w = 40, .d = 2 } },
     { PIVOT_RC_COUNT,        { 0, 0, 0 } },
     { PIVOT_RC_OTHER,        { 0, 0, 0 } },
   };
@@ -764,19 +769,35 @@ pivot_result_class_find (const char *s)
   return NULL;
 }
 
-static const struct fmt_spec *
-pivot_table_get_format (const struct pivot_table *table, const char *s)
+static void
+pivot_table_use_rc (const struct pivot_table *table, const char *s,
+                    struct fmt_spec *format, bool *honor_small)
 {
-  if (!s)
-    return NULL;
-  else if (!strcmp (s, PIVOT_RC_OTHER))
-    return settings_get_format ();
-  else if (!strcmp (s, PIVOT_RC_COUNT) && !overridden_count_format)
-    return &table->weight_format;
-  else
+  if (s)
     {
-      const struct result_class *rc = pivot_result_class_find (s);
-      return rc ? &rc->format : NULL;
+      if (!strcmp (s, PIVOT_RC_OTHER))
+        {
+          *format = *settings_get_format ();
+          *honor_small = true;
+        }
+      else if (!strcmp (s, PIVOT_RC_COUNT) && !overridden_count_format)
+        {
+          *format = table->weight_format;
+          *honor_small = false;
+        }
+      else
+        {
+          const struct result_class *rc = pivot_result_class_find (s);
+          if (rc)
+            {
+              *format = rc->format;
+              *honor_small = false;
+            }
+          else
+            {
+              printf ("unknown class %s\n", s);
+            }
+        }
     }
 }
 
@@ -844,19 +865,20 @@ pivot_table_create (const char *title)
 struct pivot_table *
 pivot_table_create__ (struct pivot_value *title, const char *subtype)
 {
-  struct pivot_table *table = xzalloc (sizeof *table);
-  table->ref_cnt = 1;
-  table->show_title = true;
-  table->show_caption = true;
-  table->weight_format = (struct fmt_spec) { FMT_F, 40, 0 };
-  table->title = title;
-  table->subtype = subtype ? pivot_value_new_text (subtype) : NULL;
-  table->command_c = output_get_command_name ();
-  table->look = pivot_table_look_ref (pivot_table_look_get_default ());
-  table->settings = fmt_settings_copy (settings_get_fmt_settings ());
-
-  hmap_init (&table->cells);
-
+  struct pivot_table *table = xmalloc (sizeof *table);
+  *table = (struct pivot_table) {
+    .ref_cnt = 1,
+    .show_title = true,
+    .show_caption = true,
+    .weight_format = (struct fmt_spec) { .type = FMT_F, .w = 40 },
+    .title = title,
+    .subtype = subtype ? pivot_value_new_text (subtype) : NULL,
+    .command_c = xstrdup_if_nonempty (output_get_command_name ()),
+    .look = pivot_table_look_ref (pivot_table_look_get_default ()),
+    .settings = fmt_settings_copy (settings_get_fmt_settings ()),
+    .small = settings_get_small (),
+    .cells = HMAP_INITIALIZER (table->cells),
+  };
   return table;
 }
 
@@ -891,12 +913,6 @@ pivot_table_ref (const struct pivot_table *table_)
   return table;
 }
 
-static char *
-xstrdup_if_nonnull (const char *s)
-{
-  return s ? xstrdup (s) : NULL;
-}
-
 static struct pivot_table_sizing
 clone_sizing (const struct pivot_table_sizing *s)
 {
@@ -952,7 +968,7 @@ clone_category (struct pivot_category *old,
     .extra_depth = old->extra_depth,
 
     .subs = (old->n_subs
-             ? xzalloc (old->n_subs * sizeof *new->subs)
+             ? xcalloc (old->n_subs, sizeof *new->subs)
              : NULL),
     .n_subs = old->n_subs,
     .allocated_subs = old->n_subs,
@@ -968,7 +984,10 @@ clone_category (struct pivot_category *old,
 
   if (pivot_category_is_leaf (old))
     {
+      assert (new->data_index < new_dimension->n_leaves);
       new->dimension->data_leaves[new->data_index] = new;
+
+      assert (new->presentation_index < new_dimension->n_leaves);
       new->dimension->presentation_leaves[new->presentation_index] = new;
     }
 
@@ -987,9 +1006,9 @@ clone_dimension (struct pivot_dimension *old, struct pivot_table *new_pt)
     .axis_type = old->axis_type,
     .level = old->level,
     .top_index = old->top_index,
-    .data_leaves = xzalloc (old->n_leaves * sizeof *new->data_leaves),
-    .presentation_leaves = xzalloc (old->n_leaves
-                                    * sizeof *new->presentation_leaves),
+    .data_leaves = xcalloc (old->n_leaves , sizeof *new->data_leaves),
+    .presentation_leaves = xcalloc (old->n_leaves
+                                    , sizeof *new->presentation_leaves),
     .n_leaves = old->n_leaves,
     .allocated_leaves = old->n_leaves,
     .hide_all_labels = old->hide_all_labels,
@@ -1172,6 +1191,43 @@ pivot_table_is_shared (const struct pivot_table *table)
   return table->ref_cnt > 1;
 }
 
+static void
+pivot_table_set_value__ (struct pivot_value **dstp, struct pivot_value *src)
+{
+  pivot_value_destroy (*dstp);
+  *dstp = src;
+}
+
+/* Changes the title of TABLE to TITLE.  Takes ownership of TITLE. */
+void
+pivot_table_set_title (struct pivot_table *table, struct pivot_value *title)
+{
+  pivot_table_set_value__ (&table->title, title);
+}
+
+/* Changes the subtype of TABLE to SUBTYPE.  Takes ownership of SUBTYPE. */
+void
+pivot_table_set_subtype (struct pivot_table *table, struct pivot_value *subtype)
+{
+  pivot_table_set_value__ (&table->subtype, subtype);
+}
+
+/* Changes the corner text of TABLE to CORNER_TEXT.  Takes ownership of
+   CORNER_TEXT. */
+void
+pivot_table_set_corner_text (struct pivot_table *table,
+                             struct pivot_value *corner_text)
+{
+  pivot_table_set_value__ (&table->corner_text, corner_text);
+}
+
+/* Changes the caption of TABLE to CAPTION.  Takes ownership of CAPTION. */
+void
+pivot_table_set_caption (struct pivot_table *table, struct pivot_value *caption)
+{
+  pivot_table_set_value__ (&table->caption, caption);
+}
+
 /* Swaps axes A and B in TABLE. */
 void
 pivot_table_swap_axes (struct pivot_table *table,
@@ -1242,7 +1298,6 @@ pivot_table_move_dimension (struct pivot_table *table,
       return;
     }
 
-
   /* Update the current layer, if necessary.  If we're moving within the layer
      axis, preserve the current layer. */
   if (dim->axis_type == PIVOT_AXIS_LAYER)
@@ -1414,11 +1469,13 @@ pivot_table_put (struct pivot_table *table, const size_t *dindexes, size_t n,
               if (c->format.w)
                 {
                   value->numeric.format = c->format;
+                  value->numeric.honor_small = c->honor_small;
                   goto done;
                 }
             }
         }
       value->numeric.format = *settings_get_format ();
+      value->numeric.honor_small = true;
 
     done:;
     }
@@ -1481,15 +1538,30 @@ pivot_table_create_footnote (struct pivot_table *table,
                                         NULL, content);
 }
 
-static struct pivot_value *
-pivot_make_default_footnote_marker (int idx, bool show_numeric_markers)
-{
-  char text[INT_BUFSIZE_BOUND (size_t)];
-  if (show_numeric_markers)
-    snprintf (text, sizeof text, "%d", idx + 1);
+void
+pivot_footnote_format_marker (const struct pivot_footnote *f,
+                              const struct pivot_table *pt,
+                              struct string *s)
+{
+  if (f->marker)
+    pivot_value_format_body (f->marker, pt, s);
+  else if (pt->look->show_numeric_markers)
+    ds_put_format (s, "%zu", f->idx + 1);
   else
-    str_format_26adic (idx + 1, false, text, sizeof text);
-  return pivot_value_new_user_text (text, -1);
+    {
+      char text[INT_BUFSIZE_BOUND (size_t)];
+      str_format_26adic (f->idx + 1, false, text, sizeof text);
+      ds_put_cstr (s, text);
+    }
+}
+
+char *
+pivot_footnote_marker_string (const struct pivot_footnote *f,
+                              const struct pivot_table *pt)
+{
+  struct string s = DS_EMPTY_INITIALIZER;
+  pivot_footnote_format_marker (f, pt, &s);
+  return ds_steal_cstr (&s);
 }
 
 /* Creates or modifies a footnote in TABLE with 0-based number IDX (and creates
@@ -1509,12 +1581,10 @@ pivot_table_create_footnote__ (struct pivot_table *table, size_t idx,
       while (idx >= table->n_footnotes)
         {
           struct pivot_footnote *f = xmalloc (sizeof *f);
-          f->idx = table->n_footnotes;
-          f->marker = pivot_make_default_footnote_marker (
-            f->idx, table->look->show_numeric_markers);
-          f->content = NULL;
-          f->show = true;
-
+          *f = (struct pivot_footnote) {
+            .idx = table->n_footnotes,
+            .show = true,
+          };
           table->footnotes[table->n_footnotes++] = f;
         }
     }
@@ -1559,8 +1629,8 @@ pivot_table_convert_indexes_ptod (const struct pivot_table *table,
       for (size_t j = 0; j < axis->n_dimensions; j++)
         {
           const struct pivot_dimension *d = axis->dimensions[j];
-          dindexes[d->top_index]
-            = d->presentation_leaves[pindexes[i][j]]->data_index;
+          size_t pindex = pindexes[i][j];
+          dindexes[d->top_index] = d->presentation_leaves[pindex]->data_index;
         }
     }
 }
@@ -2264,6 +2334,33 @@ interpret_show (enum settings_value_show global_show,
           : global_show);
 }
 
+/* Appends to OUT the actual text content from the given Pango MARKUP. */
+static void
+get_text_from_markup (const char *markup, struct string *out)
+{
+  xmlParserCtxt *parser = xmlCreatePushParserCtxt (NULL, NULL, NULL, 0, NULL);
+  if (!parser)
+    {
+      ds_put_cstr (out, markup);
+      return;
+    }
+
+  xmlParseChunk (parser, "<xml>", strlen ("<xml>"), false);
+  xmlParseChunk (parser, markup, strlen (markup), false);
+  xmlParseChunk (parser, "</xml>", strlen ("</xml>"), true);
+
+  if (parser->wellFormed)
+    {
+      xmlChar *s = xmlNodeGetContent (xmlDocGetRootElement (parser->myDoc));
+      ds_put_cstr (out, CHAR_CAST (char *, s));
+      xmlFree (s);
+    }
+  else
+    ds_put_cstr (out, markup);
+  xmlFreeDoc (parser->myDoc);
+  xmlFreeParserCtxt (parser);
+}
+
 /* Appends a text representation of the body of VALUE to OUT.  Settings on
    PT control whether variable and value labels are included.
 
@@ -2288,8 +2385,17 @@ pivot_value_format_body (const struct pivot_value *value,
                              value->numeric.value_label != NULL);
       if (show & SETTINGS_VALUE_SHOW_VALUE)
         {
+          const struct fmt_spec *f = &value->numeric.format;
+          const struct fmt_spec *format
+            = (f->type == FMT_F
+               && value->numeric.honor_small
+               && value->numeric.x != 0
+               && fabs (value->numeric.x) < pt->small
+               ? &(struct fmt_spec) { .type = FMT_E, .w = 40, .d = f->d }
+               : f);
+
           char *s = data_out (&(union value) { .f = value->numeric.x },
-                              "UTF-8", &value->numeric.format, &pt->settings);
+                              "UTF-8", format, &pt->settings);
           ds_put_cstr (out, s + strspn (s, " "));
           free (s);
         }
@@ -2342,7 +2448,10 @@ pivot_value_format_body (const struct pivot_value *value,
       break;
 
     case PIVOT_VALUE_TEXT:
-      ds_put_cstr (out, value->text.local);
+      if (value->ex && value->ex->font_style && value->ex->font_style->markup)
+        get_text_from_markup (value->text.local, out);
+      else
+        ds_put_cstr (out, value->text.local);
       break;
 
     case PIVOT_VALUE_TEMPLATE:
@@ -2357,30 +2466,39 @@ pivot_value_format_body (const struct pivot_value *value,
 /* Appends a text representation of VALUE to OUT.  Settings on
    PT control whether variable and value labels are included.
 
-   Subscripts and footnotes are included. */
-void
+   Subscripts and footnotes are included.
+
+   Returns true if OUT is a number (or a number plus a value label), false
+   otherwise.  */
+bool
 pivot_value_format (const struct pivot_value *value,
                     const struct pivot_table *pt,
                     struct string *out)
 {
-  pivot_value_format_body (value, pt, out);
+  bool numeric = pivot_value_format_body (value, pt, out);
 
-  if (value->n_subscripts)
+  const struct pivot_value_ex *ex = value->ex;
+  if (ex)
     {
-      for (size_t i = 0; i < value->n_subscripts; i++)
-        ds_put_format (out, "%c%s", i ? ',' : '_', value->subscripts[i]);
-    }
+      if (ex->n_subscripts)
+        {
+          for (size_t i = 0; i < ex->n_subscripts; i++)
+            ds_put_format (out, "%c%s", i ? ',' : '_', ex->subscripts[i]);
+        }
 
-  for (size_t i = 0; i < value->n_footnotes; i++)
-    {
-      ds_put_byte (out, '[');
+      for (size_t i = 0; i < ex->n_footnotes; i++)
+        {
+          ds_put_byte (out, '[');
 
-      size_t idx = value->footnote_indexes[i];
-      const struct pivot_footnote *f = pt->footnotes[idx];
-      pivot_value_format (f->marker, pt, out);
+          size_t idx = ex->footnote_indexes[i];
+          const struct pivot_footnote *f = pt->footnotes[idx];
+          pivot_footnote_format_marker (f, pt, out);
 
-      ds_put_byte (out, ']');
+          ds_put_byte (out, ']');
+        }
     }
+
+  return numeric;
 }
 
 /* Returns a text representation of VALUE.  The caller must free the string,
@@ -2400,6 +2518,7 @@ pivot_value_to_string_defaults (const struct pivot_value *value)
   static const struct pivot_table pt = {
     .show_values = SETTINGS_VALUE_SHOW_DEFAULT,
     .show_variables = SETTINGS_VALUE_SHOW_DEFAULT,
+    .settings = FMT_SETTINGS_INIT,
   };
   return pivot_value_to_string (value, &pt);
 }
@@ -2411,22 +2530,8 @@ pivot_value_clone (const struct pivot_value *old)
     return NULL;
 
   struct pivot_value *new = xmemdup (old, sizeof *new);
-  if (old->font_style)
-    {
-      new->font_style = xmalloc (sizeof *new->font_style);
-      font_style_copy (NULL, new->font_style, old->font_style);
-    }
-  if (old->cell_style)
-    new->font_style = xmemdup (old->font_style, sizeof *new->font_style);
-  if (old->n_subscripts)
-    {
-      new->subscripts = xnmalloc (old->n_subscripts, sizeof *new->subscripts);
-      for (size_t i = 0; i < old->n_subscripts; i++)
-        new->subscripts[i] = xstrdup (old->subscripts[i]);
-    }
-  if (old->n_footnotes)
-    new->footnote_indexes = xmemdup (
-      old->footnote_indexes, old->n_footnotes * sizeof *new->footnote_indexes);
+  if (old->ex)
+    new->ex = pivot_value_ex_clone (old->ex);
 
   switch (new->type)
     {
@@ -2449,10 +2554,10 @@ pivot_value_clone (const struct pivot_value *old)
     case PIVOT_VALUE_TEXT:
       new->text.local = xstrdup (old->text.local);
       new->text.c = (old->text.c == old->text.local ? new->text.local
-                     : xstrdup (old->text.c));
+                     : xstrdup_if_nonnull (old->text.c));
       new->text.id = (old->text.id == old->text.local ? new->text.local
                       : old->text.id == old->text.c ? new->text.c
-                      : xstrdup (old->text.id));
+                      : xstrdup_if_nonnull (old->text.id));
       break;
 
     case PIVOT_VALUE_TEMPLATE:
@@ -2479,15 +2584,7 @@ pivot_value_destroy (struct pivot_value *value)
 {
   if (value)
     {
-      font_style_uninit (value->font_style);
-      free (value->font_style);
-      free (value->cell_style);
-      free (value->footnote_indexes);
-
-      for (size_t i = 0; i < value->n_subscripts; i++)
-        free (value->subscripts[i]);
-      free (value->subscripts);
-
+      pivot_value_ex_destroy (value->ex);
       switch (value->type)
         {
         case PIVOT_VALUE_NUMERIC:
@@ -2539,12 +2636,10 @@ pivot_value_get_style (struct pivot_value *value,
                        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
-                                             : base_font_style));
-  area->cell_style = *(value->cell_style
-                       ? value->cell_style
-                       : base_cell_style);
+  const struct pivot_value_ex *ex = pivot_value_ex (value);
+  font_style_copy (NULL, &area->font_style,
+                   ex->font_style ? ex->font_style : base_font_style);
+  area->cell_style = *(ex->cell_style ? ex->cell_style : base_cell_style);
 }
 
 /* Copies AREA into VALUE's style. */
@@ -2560,20 +2655,22 @@ void
 pivot_value_set_font_style (struct pivot_value *value,
                             const struct font_style *font_style)
 {
-  if (value->font_style)
-    font_style_uninit (value->font_style);
+  struct pivot_value_ex *ex = pivot_value_ex_rw (value);
+  if (ex->font_style)
+    font_style_uninit (ex->font_style);
   else
-    value->font_style = xmalloc (sizeof *value->font_style);
-  font_style_copy (NULL, value->font_style, font_style);
+    ex->font_style = xmalloc (sizeof *ex->font_style);
+  font_style_copy (NULL, ex->font_style, font_style);
 }
 
 void
 pivot_value_set_cell_style (struct pivot_value *value,
                             const struct cell_style *cell_style)
 {
-  if (!value->cell_style)
-    value->cell_style = xmalloc (sizeof *value->cell_style);
-  *value->cell_style = *cell_style;
+  struct pivot_value_ex *ex = pivot_value_ex_rw (value);
+  if (!ex->cell_style)
+    ex->cell_style = xmalloc (sizeof *ex->cell_style);
+  *ex->cell_style = *cell_style;
 }
 
 void
@@ -2614,8 +2711,8 @@ pivot_value_new_user_text_nocopy (char *text)
 {
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_TEXT,
     .text = {
+      .type = PIVOT_VALUE_TEXT,
       .local = text,
       .c = text,
       .id = text,
@@ -2633,9 +2730,9 @@ pivot_value_new_user_text_nocopy (char *text)
    that pivot_value_new_variable() should be used for variable names).  For
    strings that are part of the PSPP user interface, such as names of
    procedures, statistics, annotations, error messages, etc., use
-   pivot_value_new_text().j
+   pivot_value_new_text().
 
-   The caller retains ownership of TEXT.*/
+   The caller retains ownership of TEXT. */
 struct pivot_value *
 pivot_value_new_user_text (const char *text, size_t length)
 {
@@ -2657,8 +2754,8 @@ pivot_value_new_text (const char *text)
 
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_TEXT,
     .text = {
+      .type = PIVOT_VALUE_TEXT,
       .local = local,
       .c = c,
       .id = c,
@@ -2684,8 +2781,8 @@ pivot_value_new_text_format (const char *format, ...)
 
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_TEXT,
     .text = {
+      .type = PIVOT_VALUE_TEXT,
       .local = local,
       .c = c,
       .id = xstrdup (c),
@@ -2706,8 +2803,10 @@ pivot_value_new_number (double x)
 {
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_NUMERIC,
-    .numeric = { .x = x, },
+    .numeric = {
+      .type = PIVOT_VALUE_NUMERIC,
+      .x = x
+    },
   };
   return value;
 }
@@ -2717,7 +2816,7 @@ struct pivot_value *
 pivot_value_new_integer (double x)
 {
   struct pivot_value *value = pivot_value_new_number (x);
-  value->numeric.format = (struct fmt_spec) { FMT_F, 40, 0 };
+  value->numeric.format = (struct fmt_spec) { .type = FMT_F, .w = 40 };
   return value;
 }
 
@@ -2756,7 +2855,7 @@ struct pivot_value *
 pivot_value_new_value (const union value *value, int width,
                        const struct fmt_spec *format, const char *encoding)
 {
-  struct pivot_value *pv = xzalloc (sizeof *pv);
+  struct pivot_value *pv = XZALLOC (struct pivot_value);
   if (width > 0)
     {
       char *s = recode_string (UTF8, encoding, CHAR_CAST (char *, value->s),
@@ -2782,13 +2881,22 @@ pivot_value_new_value (const union value *value, int width,
 /* Returns a new pivot_value for VARIABLE. */
 struct pivot_value *
 pivot_value_new_variable (const struct variable *variable)
+{
+  return pivot_value_new_variable__ (var_get_name (variable),
+                                     var_get_label (variable));
+}
+
+/* Returns a new pivot_value for a variable with the given NAME and optional
+   LABEL. */
+struct pivot_value *
+pivot_value_new_variable__ (const char *name, const char *label)
 {
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_VARIABLE,
     .variable = {
-      .var_name = xstrdup (var_get_name (variable)),
-      .var_label = xstrdup_if_nonempty (var_get_label (variable)),
+      .type = PIVOT_VALUE_VARIABLE,
+      .var_name = xstrdup (name),
+      .var_label = xstrdup_if_nonempty (label),
     },
   };
   return value;
@@ -2799,15 +2907,41 @@ void
 pivot_value_add_footnote (struct pivot_value *v,
                           const struct pivot_footnote *footnote)
 {
+  struct pivot_value_ex *ex = pivot_value_ex_rw (v);
+
   /* Some legacy tables include numerous duplicate footnotes.  Suppress
      them. */
-  for (size_t i = 0; i < v->n_footnotes; i++)
-    if (v->footnote_indexes[i] == footnote->idx)
+  for (size_t i = 0; i < ex->n_footnotes; i++)
+    if (ex->footnote_indexes[i] == footnote->idx)
       return;
 
-  v->footnote_indexes = xrealloc (
-    v->footnote_indexes, (v->n_footnotes + 1) * sizeof *v->footnote_indexes);
-  v->footnote_indexes[v->n_footnotes++] = footnote->idx;
+  ex->footnote_indexes = xrealloc (
+    ex->footnote_indexes,
+    (ex->n_footnotes + 1) * sizeof *ex->footnote_indexes);
+  ex->footnote_indexes[ex->n_footnotes++] = footnote->idx;
+  pivot_value_sort_footnotes (v);
+}
+
+static int
+compare_footnote_indexes (const void *a_, const void *b_)
+{
+  const size_t *ap = a_;
+  const size_t *bp = b_;
+  size_t a = *ap;
+  size_t b = *bp;
+  return a < b ? -1 : a > b;
+}
+
+/* Sorts the footnote references in V in the standard ascending order.
+
+   This is only necessary if code adds (plural) footnotes to a pivot_value by
+   itself, because pivot_value_add_footnote() does it automatically. */
+void
+pivot_value_sort_footnotes (struct pivot_value *v)
+{
+  if (v->ex && v->ex->n_footnotes > 1)
+    qsort (v->ex->footnote_indexes, v->ex->n_footnotes,
+           sizeof *v->ex->footnote_indexes, compare_footnote_indexes);
 }
 
 /* If VALUE is a numeric value, and RC is a result class such as
@@ -2817,10 +2951,69 @@ pivot_value_set_rc (const struct pivot_table *table, struct pivot_value *value,
                     const char *rc)
 {
   if (value->type == PIVOT_VALUE_NUMERIC)
+    pivot_table_use_rc (table, rc,
+                        &value->numeric.format, &value->numeric.honor_small);
+}
+\f
+/* pivot_value_ex. */
+
+struct pivot_value_ex *
+pivot_value_ex_rw (struct pivot_value *value)
+{
+  if (!value->ex)
+    value->ex = xzalloc (sizeof *value->ex);
+  return value->ex;
+}
+
+struct pivot_value_ex *
+pivot_value_ex_clone (const struct pivot_value_ex *old)
+{
+  struct font_style *font_style = NULL;
+  if (old->font_style)
     {
-      const struct fmt_spec *f = pivot_table_get_format (table, rc);
-      if (f)
-        value->numeric.format = *f;
+      font_style = xmalloc (sizeof *font_style);
+      font_style_copy (NULL, font_style, old->font_style);
     }
+
+  char **subscripts = NULL;
+  if (old->n_subscripts)
+    {
+      subscripts = xnmalloc (old->n_subscripts, sizeof *subscripts);
+      for (size_t i = 0; i < old->n_subscripts; i++)
+        subscripts[i] = xstrdup (old->subscripts[i]);
+    }
+
+  struct pivot_value_ex *new = xmalloc (sizeof *new);
+  *new = (struct pivot_value_ex) {
+    .font_style = font_style,
+    .cell_style = (old->cell_style
+                   ? xmemdup (old->cell_style, sizeof *new->cell_style)
+                   : NULL),
+    .subscripts = subscripts,
+    .n_subscripts = old->n_subscripts,
+    .footnote_indexes = (
+      old->n_footnotes
+      ? xmemdup (old->footnote_indexes,
+                 old->n_footnotes * sizeof *new->footnote_indexes)
+      : NULL),
+    .n_footnotes = old->n_footnotes
+  };
+  return new;
 }
 
+void
+pivot_value_ex_destroy (struct pivot_value_ex *ex)
+{
+  if (ex)
+    {
+      font_style_uninit (ex->font_style);
+      free (ex->font_style);
+      free (ex->cell_style);
+      free (ex->footnote_indexes);
+
+      for (size_t i = 0; i < ex->n_subscripts; i++)
+        free (ex->subscripts[i]);
+      free (ex->subscripts);
+      free (ex);
+    }
+}