#include "output/pivot-table.h"
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <math.h>
#include <stdlib.h>
#include "data/data-out.h"
#include "data/settings.h"
#include "data/value.h"
#include "data/variable.h"
+#include "data/file-name.h"
+#include "libpspp/array.h"
+#include "libpspp/assertion.h"
#include "libpspp/hash-functions.h"
#include "libpspp/i18n.h"
+#include "output/driver.h"
+#include "output/spv/spv-table-look.h"
#include "gl/c-ctype.h"
+#include "gl/configmake.h"
#include "gl/intprops.h"
#include "gl/minmax.h"
+#include "gl/relocatable.h"
#include "gl/xalloc.h"
#include "gl/xmemdup0.h"
#include "gl/xsize.h"
#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. */
}
}
\f
+/* Pivot table looks. */
+
+static const struct pivot_table_look *
+default_look (const struct pivot_table_look *new)
+{
+ static struct pivot_table_look *look;
+ if (new)
+ {
+ pivot_table_look_unref (look);
+ look = pivot_table_look_ref (new);
+ }
+ else if (!look)
+ {
+ char *error = pivot_table_look_read ("default.stt", &look);
+ if (error)
+ {
+ free (error);
+ look = pivot_table_look_ref (pivot_table_look_builtin_default ());
+ }
+ }
+ return look;
+}
+
+const struct pivot_table_look *
+pivot_table_look_get_default (void)
+{
+ return default_look (NULL);
+}
+
+void
+pivot_table_look_set_default (const struct pivot_table_look *look)
+{
+ default_look (look);
+}
+
+char * WARN_UNUSED_RESULT
+pivot_table_look_read (const char *name, struct pivot_table_look **lookp)
+{
+ *lookp = NULL;
+
+ /* Construct search path. */
+ const char *path[4];
+ size_t n = 0;
+ path[n++] = ".";
+ const char *home = getenv ("HOME");
+ char *allocated = NULL;
+ if (home != NULL)
+ path[n++] = allocated = xasprintf ("%s/.pspp/looks", home);
+ char *allocated2;
+ path[n++] = relocate2 (PKGDATADIR "/looks", &allocated2);
+ path[n++] = NULL;
+
+ /* Search path. */
+ char *file = fn_search_path (name, (char **) path);
+ if (!file)
+ {
+ char *name2 = xasprintf ("%s.stt", name);
+ file = fn_search_path (name2, (char **) path);
+ free (name2);
+ }
+ free (allocated);
+ free (allocated2);
+ if (!file)
+ return xasprintf ("%s: not found", name);
+
+ /* Read file. */
+ char *error = spv_table_look_read (file, lookp);
+ free (file);
+ return error;
+}
+
+const struct pivot_table_look *
+pivot_table_look_builtin_default (void)
+{
+ static struct pivot_table_look look = {
+ .ref_cnt = 1,
+
+ .omit_empty = true,
+ .row_labels_in_corner = true,
+ .width_ranges = {
+ [TABLE_HORZ] = { 36, 72 },
+ [TABLE_VERT] = { 36, 120 },
+ },
+
+ .areas = {
+#define AREA(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, \
+ .typeface = (char *) "Sans Serif", \
+ }, \
+ }
+ [PIVOT_AREA_TITLE] = AREA(true, CENTER, CENTER, 8,11,1,8),
+ [PIVOT_AREA_CAPTION] = AREA(false, LEFT, TOP, 8,11,1,1),
+ [PIVOT_AREA_FOOTER] = AREA(false, LEFT, TOP, 11, 8,2,3),
+ [PIVOT_AREA_CORNER] = AREA(false, LEFT, BOTTOM, 8,11,1,1),
+ [PIVOT_AREA_COLUMN_LABELS] = AREA(false, CENTER, BOTTOM, 8,11,1,3),
+ [PIVOT_AREA_ROW_LABELS] = AREA(false, LEFT, TOP, 8,11,1,3),
+ [PIVOT_AREA_DATA] = AREA(false, MIXED, TOP, 8,11,1,1),
+ [PIVOT_AREA_LAYERS] = AREA(false, LEFT, BOTTOM, 8,11,1,3),
+#undef AREA
+ },
+
+ .borders = {
+#define BORDER(STROKE) { .stroke = STROKE, .color = CELL_COLOR_BLACK }
+ [PIVOT_BORDER_TITLE] = BORDER(TABLE_STROKE_NONE),
+ [PIVOT_BORDER_OUTER_LEFT] = BORDER(TABLE_STROKE_NONE),
+ [PIVOT_BORDER_OUTER_TOP] = BORDER(TABLE_STROKE_NONE),
+ [PIVOT_BORDER_OUTER_RIGHT] = BORDER(TABLE_STROKE_NONE),
+ [PIVOT_BORDER_OUTER_BOTTOM] = BORDER(TABLE_STROKE_NONE),
+ [PIVOT_BORDER_INNER_LEFT] = BORDER(TABLE_STROKE_THICK),
+ [PIVOT_BORDER_INNER_TOP] = BORDER(TABLE_STROKE_THICK),
+ [PIVOT_BORDER_INNER_RIGHT] = BORDER(TABLE_STROKE_THICK),
+ [PIVOT_BORDER_INNER_BOTTOM] = BORDER(TABLE_STROKE_THICK),
+ [PIVOT_BORDER_DATA_LEFT] = BORDER(TABLE_STROKE_THICK),
+ [PIVOT_BORDER_DATA_TOP] = BORDER(TABLE_STROKE_THICK),
+ [PIVOT_BORDER_DIM_ROW_HORZ] = BORDER(TABLE_STROKE_SOLID),
+ [PIVOT_BORDER_DIM_ROW_VERT] = BORDER(TABLE_STROKE_NONE),
+ [PIVOT_BORDER_DIM_COL_HORZ] = BORDER(TABLE_STROKE_SOLID),
+ [PIVOT_BORDER_DIM_COL_VERT] = BORDER(TABLE_STROKE_SOLID),
+ [PIVOT_BORDER_CAT_ROW_HORZ] = BORDER(TABLE_STROKE_NONE),
+ [PIVOT_BORDER_CAT_ROW_VERT] = BORDER(TABLE_STROKE_NONE),
+ [PIVOT_BORDER_CAT_COL_HORZ] = BORDER(TABLE_STROKE_SOLID),
+ [PIVOT_BORDER_CAT_COL_VERT] = BORDER(TABLE_STROKE_SOLID),
+ },
+ };
+
+ return &look;
+}
+
+struct pivot_table_look *
+pivot_table_look_new_builtin_default (void)
+{
+ return pivot_table_look_unshare (
+ pivot_table_look_ref (pivot_table_look_builtin_default ()));
+}
+
+struct pivot_table_look *
+pivot_table_look_ref (const struct pivot_table_look *look_)
+{
+ assert (look_->ref_cnt > 0);
+
+ struct pivot_table_look *look = CONST_CAST (struct pivot_table_look *, look_);
+ look->ref_cnt++;
+ return look;
+}
+
+static char *
+xstrdup_if_nonempty (const char *s)
+{
+ return s && s[0] ? xstrdup (s) : NULL;
+}
+
+struct pivot_table_look *
+pivot_table_look_unshare (struct pivot_table_look *old)
+{
+ assert (old->ref_cnt > 0);
+ if (old->ref_cnt == 1)
+ return old;
+
+ pivot_table_look_unref (old);
+
+ struct pivot_table_look *new = xmemdup (old, sizeof *old);
+ new->ref_cnt = 1;
+ new->name = xstrdup_if_nonempty (old->name);
+ for (size_t i = 0; i < PIVOT_N_AREAS; i++)
+ table_area_style_copy (NULL, &new->areas[i], &old->areas[i]);
+ new->continuation = xstrdup_if_nonempty (old->continuation);
+
+ return new;
+}
+
+void
+pivot_table_look_unref (struct pivot_table_look *look)
+{
+ if (look)
+ {
+ assert (look->ref_cnt > 0);
+ if (!--look->ref_cnt)
+ {
+ free (look->name);
+ for (size_t i = 0; i < PIVOT_N_AREAS; i++)
+ table_area_style_uninit (&look->areas[i]);
+ free (look->continuation);
+ free (look);
+ }
+ }
+}
+\f
/* Axes. */
/* Returns the name of AXIS_TYPE. */
if (axis->dimensions[i]->n_leaves == 0)
return NULL;
- return xcalloc (axis->n_dimensions, sizeof *indexes);
+ size_t size = axis->n_dimensions * sizeof *indexes;
+ return xzalloc (MAX (size, 1));
}
for (size_t i = 0; i < axis->n_dimensions; i++)
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)
+ pivot_table_use_rc (category->dimension->table, s,
+ &name->numeric.format, &name->numeric.honor_small);
}
static void
sizeof *table->current_layer);
}
- /* XXX extent and label_depth need to be calculated later. */
+ /* axis->extent and axis->label_depth will be calculated later. */
return d;
}
/* 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 } },
};
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);
+ }
+ }
}
}
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. */
- };
+bool
+is_pivot_result_class (const char *s)
+{
+ return pivot_result_class_find (s) != NULL;
+}
+\f
/* Pivot tables. */
+static struct pivot_cell *pivot_table_insert_cell (struct pivot_table *,
+ const size_t *dindexes);
+static void pivot_table_delete_cell (struct pivot_table *,
+ struct pivot_cell *);
+
/* Creates and returns a new pivot table with the given TITLE. TITLE should be
a text string marked for translation but not actually translated yet,
- e.g. N_("Descriptive Statistics").
-
- Operations commonly performed on the new pivot_table:
-
- - If empty rows or columns should not be displayed, set ->omit_empty to
- true.
-
- - Set the format to use for "count" values with pivot_table_set_weight_var()
- or pivot_table_set_weight_format().
+ e.g. N_("Descriptive Statistics"). The un-translated text string is used as
+ the pivot table's subtype.
This function is a shortcut for pivot_table_create__() for the most common
case. Use pivot_table_create__() directly if the title should be some kind
- of value other than an ordinary text string.
+ of value other than an ordinary text string, or if the subtype should be
+ different from the title.
See the large comment at the top of pivot-table.h for general advice on
creating pivot tables. */
struct pivot_table *
pivot_table_create (const char *title)
{
- return pivot_table_create__ (pivot_value_new_text (title));
+ return pivot_table_create__ (pivot_value_new_text (title), title);
}
/* Creates and returns a new pivot table with the given TITLE, and takes
- ownership of TITLE.
+ ownership of TITLE. The new pivot table's subtype is SUBTYPE, which should
+ be an untranslated English string that describes the contents of the table
+ at a high level without being specific about the variables or other context
+ involved.
- Operations commonly performed on the new pivot_table:
-
- - If empty rows or columns should not be displayed, set ->omit_empty to
- true.
-
- - Set the format to use for "count" values with pivot_table_set_weight_var()
- or pivot_table_set_weight_format().
+ TITLE and SUBTYPE may be NULL, but in that case the client must add them
+ later because they are both mandatory for a pivot table.
See the large comment at the top of pivot-table.h for general advice on
creating pivot tables. */
struct pivot_table *
-pivot_table_create__ (struct pivot_value *title)
+pivot_table_create__ (struct pivot_value *title, const char *subtype)
{
struct pivot_table *table = xzalloc (sizeof *table);
- table->weight_format = (struct fmt_spec) { FMT_F, 40, 0 };
+ table->ref_cnt = 1;
+ table->show_title = true;
+ table->show_caption = true;
+ table->weight_format = (struct fmt_spec) { .type = FMT_F, .w = 40 };
table->title = title;
+ table->subtype = subtype ? pivot_value_new_text (subtype) : NULL;
+ table->command_c = xstrdup_if_nonempty (output_get_command_name ());
+ table->look = pivot_table_look_ref (pivot_table_look_get_default ());
+ table->settings = fmt_settings_copy (settings_get_fmt_settings ());
+ table->small = settings_get_small ();
- /* 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
- for (size_t i = 0; i < PIVOT_N_AREAS; i++)
- table->areas[i] = default_area_styles[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;
pivot_table_create_for_text (struct pivot_value *title,
struct pivot_value *content)
{
- struct pivot_table *table = pivot_table_create__ (title);
+ struct pivot_table *table = pivot_table_create__ (title, "Error");
struct pivot_dimension *d = pivot_dimension_create (
table, PIVOT_AXIS_ROW, N_("Error"));
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;
+}
+
+static struct pivot_table_sizing
+clone_sizing (const struct pivot_table_sizing *s)
+{
+ return (struct pivot_table_sizing) {
+ .widths = (s->n_widths
+ ? xmemdup (s->widths, s->n_widths * sizeof *s->widths)
+ : NULL),
+ .n_widths = s->n_widths,
+
+ .breaks = (s->n_breaks
+ ? xmemdup (s->breaks, s->n_breaks * sizeof *s->breaks)
+ : NULL),
+ .n_breaks = s->n_breaks,
+
+ .keeps = (s->n_keeps
+ ? xmemdup (s->keeps, s->n_keeps * sizeof *s->keeps)
+ : NULL),
+ .n_keeps = s->n_keeps,
+ };
+}
+
+static struct pivot_footnote **
+clone_footnotes (struct pivot_footnote **old, size_t n)
+{
+ if (!n)
+ return NULL;
+
+ struct pivot_footnote **new = xmalloc (n * sizeof *new);
+ for (size_t i = 0; i < n; i++)
+ {
+ new[i] = xmalloc (sizeof *new[i]);
+ *new[i] = (struct pivot_footnote) {
+ .idx = old[i]->idx,
+ .content = pivot_value_clone (old[i]->content),
+ .marker = pivot_value_clone (old[i]->marker),
+ .show = old[i]->show,
+ };
+ }
+ return new;
+}
+
+static struct pivot_category *
+clone_category (struct pivot_category *old,
+ struct pivot_dimension *new_dimension,
+ struct pivot_category *new_parent)
+{
+ struct pivot_category *new = xmalloc (sizeof *new);
+ *new = (struct pivot_category) {
+ .name = pivot_value_clone (old->name),
+ .parent = new_parent,
+ .dimension = new_dimension,
+ .label_depth = old->label_depth,
+ .extra_depth = old->extra_depth,
+
+ .subs = (old->n_subs
+ ? xzalloc (old->n_subs * sizeof *new->subs)
+ : NULL),
+ .n_subs = old->n_subs,
+ .allocated_subs = old->n_subs,
+
+ .show_label = old->show_label,
+ .show_label_in_corner = old->show_label_in_corner,
+
+ .format = old->format,
+ .group_index = old->group_index,
+ .data_index = old->data_index,
+ .presentation_index = old->presentation_index,
+ };
+
+ 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;
+ }
+
+ for (size_t i = 0; i < new->n_subs; i++)
+ new->subs[i] = clone_category (old->subs[i], new_dimension, new);
+
+ return new;
+}
+
+static struct pivot_dimension *
+clone_dimension (struct pivot_dimension *old, struct pivot_table *new_pt)
+{
+ struct pivot_dimension *new = xmalloc (sizeof *new);
+ *new = (struct pivot_dimension) {
+ .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),
+ .n_leaves = old->n_leaves,
+ .allocated_leaves = old->n_leaves,
+ .hide_all_labels = old->hide_all_labels,
+ .label_depth = old->label_depth,
+ };
+
+ new->root = clone_category (old->root, new, NULL);
+
+ return new;
+}
+
+static struct pivot_dimension **
+clone_dimensions (struct pivot_dimension **old, size_t n,
+ struct pivot_table *new_pt)
+{
+ if (!n)
+ return NULL;
+
+ struct pivot_dimension **new = xmalloc (n * sizeof *new);
+ for (size_t i = 0; i < n; i++)
+ new[i] = clone_dimension (old[i], new_pt);
+ return new;
+}
+
+struct pivot_table *
+pivot_table_unshare (struct pivot_table *old)
+{
+ assert (old->ref_cnt > 0);
+ if (old->ref_cnt == 1)
+ return old;
+
+ pivot_table_unref (old);
+
+ struct pivot_table *new = xmalloc (sizeof *new);
+ *new = (struct pivot_table) {
+ .ref_cnt = 1,
+
+ .look = pivot_table_look_ref (old->look),
+
+ .rotate_inner_column_labels = old->rotate_inner_column_labels,
+ .rotate_outer_row_labels = old->rotate_outer_row_labels,
+ .show_grid_lines = old->show_grid_lines,
+ .show_title = old->show_title,
+ .show_caption = old->show_caption,
+ .current_layer = (old->current_layer
+ ? xmemdup (old->current_layer,
+ old->axes[PIVOT_AXIS_LAYER].n_dimensions
+ * sizeof *new->current_layer)
+ : NULL),
+ .show_values = old->show_values,
+ .show_variables = old->show_variables,
+ .weight_format = old->weight_format,
+
+ .sizing = {
+ [TABLE_HORZ] = clone_sizing (&old->sizing[TABLE_HORZ]),
+ [TABLE_VERT] = clone_sizing (&old->sizing[TABLE_VERT]),
+ },
+
+ .settings = fmt_settings_copy (&old->settings),
+ .grouping = old->grouping,
+ .small = old->small,
+
+ .command_local = xstrdup_if_nonnull (old->command_local),
+ .command_c = xstrdup_if_nonnull (old->command_c),
+ .language = xstrdup_if_nonnull (old->language),
+ .locale = xstrdup_if_nonnull (old->locale),
+
+ .dataset = xstrdup_if_nonnull (old->dataset),
+ .datafile = xstrdup_if_nonnull (old->datafile),
+ .date = old->date,
+
+ .footnotes = clone_footnotes (old->footnotes, old->n_footnotes),
+ .n_footnotes = old->n_footnotes,
+ .allocated_footnotes = old->n_footnotes,
+
+ .title = pivot_value_clone (old->title),
+ .subtype = pivot_value_clone (old->subtype),
+ .corner_text = pivot_value_clone (old->corner_text),
+ .caption = pivot_value_clone (old->caption),
+ .notes = xstrdup_if_nonnull (old->notes),
+
+ .dimensions = clone_dimensions (old->dimensions, old->n_dimensions, new),
+ .n_dimensions = old->n_dimensions,
+
+ .cells = HMAP_INITIALIZER (new->cells),
+ };
+
+ for (size_t i = 0; i < PIVOT_N_AXES; i++)
+ {
+ struct pivot_axis *new_axis = &new->axes[i];
+ const struct pivot_axis *old_axis = &old->axes[i];
+
+ *new_axis = (struct pivot_axis) {
+ .dimensions = xmalloc (old_axis->n_dimensions
+ * sizeof *new_axis->dimensions),
+ .n_dimensions = old_axis->n_dimensions,
+ .extent = old_axis->extent,
+ .label_depth = old_axis->label_depth,
+ };
+
+ for (size_t i = 0; i < new_axis->n_dimensions; i++)
+ new_axis->dimensions[i] = new->dimensions[
+ old_axis->dimensions[i]->top_index];
+ }
+
+ const struct pivot_cell *old_cell;
+ size_t *dindexes = xmalloc (old->n_dimensions * sizeof *dindexes);
+ HMAP_FOR_EACH (old_cell, struct pivot_cell, hmap_node, &old->cells)
+ {
+ for (size_t i = 0; i < old->n_dimensions; i++)
+ dindexes[i] = old_cell->idx[i];
+ struct pivot_cell *new_cell
+ = pivot_table_insert_cell (new, dindexes);
+ new_cell->value = pivot_value_clone (old_cell->value);
+ }
+ free (dindexes);
+
+ return new;
+}
+
+/* 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);
+ pivot_table_look_unref (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]);
+ fmt_settings_uninit (&table->settings);
free (table->command_local);
free (table->command_c);
pivot_value_destroy (table->subtype);
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]);
+ free (table->notes);
for (size_t i = 0; i < table->n_dimensions; i++)
pivot_dimension_destroy (table->dimensions[i]);
struct pivot_cell *cell, *next_cell;
HMAP_FOR_EACH_SAFE (cell, next_cell, struct pivot_cell, hmap_node,
&table->cells)
- {
- hmap_delete (&table->cells, &cell->hmap_node);
- pivot_value_destroy (cell->value);
- free (cell);
- }
+ pivot_table_delete_cell (table, cell);
+
hmap_destroy (&table->cells);
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;
+}
+
+/* Swaps axes A and B in TABLE. */
+void
+pivot_table_swap_axes (struct pivot_table *table,
+ enum pivot_axis_type a, enum pivot_axis_type b)
+{
+ if (a == b)
+ return;
+
+ struct pivot_axis tmp = table->axes[a];
+ table->axes[a] = table->axes[b];
+ table->axes[b] = tmp;
+
+ for (int a = 0; a < PIVOT_N_AXES; a++)
+ {
+ struct pivot_axis *axis = &table->axes[a];
+ for (size_t d = 0; d < axis->n_dimensions; d++)
+ axis->dimensions[d]->axis_type = a;
+ }
+
+ if (a == PIVOT_AXIS_LAYER || b == PIVOT_AXIS_LAYER)
+ {
+ free (table->current_layer);
+ table->current_layer = xzalloc (
+ table->axes[PIVOT_AXIS_LAYER].n_dimensions
+ * sizeof *table->current_layer);
+ }
+}
+
+/* Swaps the row and column axes in TABLE. */
+void
+pivot_table_transpose (struct pivot_table *table)
+{
+ pivot_table_swap_axes (table, PIVOT_AXIS_ROW, PIVOT_AXIS_COLUMN);
+}
+
+static void
+pivot_table_update_axes (struct pivot_table *table)
+{
+ for (int a = 0; a < PIVOT_N_AXES; a++)
+ {
+ struct pivot_axis *axis = &table->axes[a];
+
+ for (size_t d = 0; d < axis->n_dimensions; d++)
+ {
+ struct pivot_dimension *dim = axis->dimensions[d];
+ dim->axis_type = a;
+ dim->level = d;
+ }
+ }
+}
+
+/* Moves DIM from its current location in TABLE to POS within AXIS. POS of 0
+ is the innermost dimension, 1 is the next one out, and so on. */
+void
+pivot_table_move_dimension (struct pivot_table *table,
+ struct pivot_dimension *dim,
+ enum pivot_axis_type axis, size_t pos)
+{
+ assert (dim->table == table);
+
+ struct pivot_axis *old_axis = &table->axes[dim->axis_type];
+ struct pivot_axis *new_axis = &table->axes[axis];
+ pos = MIN (pos, new_axis->n_dimensions);
+
+ if (old_axis == new_axis && pos == dim->level)
+ {
+ /* No change. */
+ 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)
+ {
+ if (axis == PIVOT_AXIS_LAYER)
+ {
+ /* Rearranging the layer axis. */
+ move_element (table->current_layer, old_axis->n_dimensions,
+ sizeof *table->current_layer,
+ dim->level, pos);
+ }
+ else
+ {
+ /* A layer is becoming a row or column. */
+ remove_element (table->current_layer, old_axis->n_dimensions,
+ sizeof *table->current_layer, dim->level);
+ }
+ }
+ else if (axis == PIVOT_AXIS_LAYER)
+ {
+ /* A row or column is becoming a layer. */
+ table->current_layer = xrealloc (
+ table->current_layer,
+ (new_axis->n_dimensions + 1) * sizeof *table->current_layer);
+ insert_element (table->current_layer, new_axis->n_dimensions,
+ sizeof *table->current_layer, pos);
+ table->current_layer[pos] = 0;
+ }
+
+ /* Remove DIM from its current axis. */
+ remove_element (old_axis->dimensions, old_axis->n_dimensions,
+ sizeof *old_axis->dimensions, dim->level);
+ old_axis->n_dimensions--;
+
+ /* Insert DIM into its new axis. */
+ new_axis->dimensions = xrealloc (
+ new_axis->dimensions,
+ (new_axis->n_dimensions + 1) * sizeof *new_axis->dimensions);
+ insert_element (new_axis->dimensions, new_axis->n_dimensions,
+ sizeof *new_axis->dimensions, pos);
+ new_axis->dimensions[pos] = dim;
+ new_axis->n_dimensions++;
+
+ pivot_table_update_axes (table);
+}
+
+
+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_unref (table->look);
+ table->look = pivot_table_look_ref (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.
struct pivot_value *value)
{
assert (n == table->n_dimensions);
+ for (size_t i = 0; i < n; i++)
+ assert (dindexes[i] < table->dimensions[i]->n_leaves);
if (value->type == PIVOT_VALUE_NUMERIC && !value->numeric.format.w)
{
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:;
}
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);
+ }
}
-/* 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. */
+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
+ 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,
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->show_numeric_markers);
- f->content = NULL;
-
+ *f = (struct pivot_footnote) {
+ .idx = table->n_footnotes,
+ .show = true,
+ };
table->footnotes[table->n_footnotes++] = f;
}
}
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;
}
}
}
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)
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;
return enumeration;
}
-static const struct pivot_cell *
+static struct pivot_cell *
pivot_table_lookup_cell (const struct pivot_table *table,
- const size_t *dindexes)
+ const size_t *dindexes)
{
unsigned int hash = pivot_cell_hash_indexes (dindexes, table->n_dimensions);
return pivot_table_lookup_cell__ (table, dindexes, hash);
return cell->value;
}
+static void
+pivot_table_delete_cell (struct pivot_table *table, struct pivot_cell *cell)
+{
+ hmap_delete (&table->cells, &cell->hmap_node);
+ pivot_value_destroy (cell->value);
+ free (cell);
+}
+
+bool
+pivot_table_delete (struct pivot_table *table, const size_t *dindexes)
+{
+ struct pivot_cell *cell = pivot_table_lookup_cell (table, dindexes);
+ if (cell)
+ {
+ pivot_table_delete_cell (table, cell);
+ return true;
+ }
+ else
+ return false;
+}
+
static void
distribute_extra_depth (struct pivot_category *category, size_t extra_depth)
{
{
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;
pivot_axis_assign_label_depth (table, PIVOT_AXIS_LAYER, false);
}
\f
-/* Footnotes. */
-
-\f
-\f
static void
indent (int indentation)
{
}
static void
-pivot_value_dump (const struct pivot_value *value)
+pivot_value_dump (const struct pivot_value *value,
+ const struct pivot_table *pt)
{
- char *s = pivot_value_to_string (value, SETTINGS_VALUE_SHOW_DEFAULT,
- SETTINGS_VALUE_SHOW_DEFAULT);
+ char *s = pivot_value_to_string (value, pt);
fputs (s, stdout);
free (s);
}
static void
pivot_table_dump_value (const struct pivot_value *value, const char *name,
- int indentation)
+ const struct pivot_table *pt, int indentation)
{
if (value)
{
indent (indentation);
printf ("%s: ", name);
- pivot_value_dump (value);
+ pivot_value_dump (value, pt);
putchar ('\n');
}
}
}
static void
-pivot_category_dump (const struct pivot_category *c, int indentation)
+pivot_category_dump (const struct pivot_category *c,
+ const struct pivot_table *pt, int indentation)
{
indent (indentation);
printf ("%s \"", pivot_category_is_leaf (c) ? "leaf" : "group");
- pivot_value_dump (c->name);
+ pivot_value_dump (c->name, pt);
printf ("\" ");
if (pivot_category_is_leaf (c))
printf ("\n");
for (size_t i = 0; i < c->n_subs; i++)
- pivot_category_dump (c->subs[i], indentation + 1);
+ pivot_category_dump (c->subs[i], pt, indentation + 1);
}
}
void
-pivot_dimension_dump (const struct pivot_dimension *d, int indentation)
+pivot_dimension_dump (const struct pivot_dimension *d,
+ const struct pivot_table *pt, int indentation)
{
indent (indentation);
printf ("%s dimension %zu (where 0=innermost), label_depth=%d:\n",
pivot_axis_type_to_string (d->axis_type), d->level, d->label_depth);
- pivot_category_dump (d->root, indentation + 1);
+ pivot_category_dump (d->root, pt, indentation + 1);
}
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));
}
static char ***
-compose_headings (const struct pivot_axis *axis,
- const size_t *column_enumeration,
- enum settings_value_show show_values,
- enum settings_value_show show_variables)
+compose_headings (const struct pivot_table *pt,
+ const struct pivot_axis *axis,
+ const size_t *column_enumeration)
{
if (!axis->n_dimensions || !axis->extent || !axis->label_depth)
return NULL;
if (pivot_category_is_leaf (c) || (c->show_label
&& !c->show_label_in_corner))
{
- headings[row][column] = pivot_value_to_string (
- c->name, show_values, show_variables);
+ headings[row][column] = pivot_value_to_string (c->name, pt);
if (!*headings[row][column])
headings[row][column] = xstrdup ("<blank>");
row--;
free (headings);
}
+static void
+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, width_ranges[0], width_ranges[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)
{
if (!table)
return;
- int old_decimal = settings_get_decimal_char (FMT_COMMA);
- if (table->decimal == '.' || table->decimal == ',')
- settings_set_decimal_char (table->decimal);
+ pivot_table_assign_label_depth (CONST_CAST (struct pivot_table *, table));
- pivot_table_dump_value (table->title, "title", indentation);
+ pivot_table_dump_value (table->title, "title", table, indentation);
+ pivot_table_dump_value (table->subtype, "subtype", table, indentation);
pivot_table_dump_string (table->command_c, "command", 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);
- printf ("date: %s", ctime (&table->date)); /* XXX thread unsafe */
+
+ 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->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);
+ pivot_dimension_dump (table->dimensions[i], table, 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)
for (size_t i = 0; i < layer_axis->n_dimensions; i++)
{
const struct pivot_dimension *d = layer_axis->dimensions[i];
- char *name = pivot_value_to_string (d->root->name,
- table->show_values,
- table->show_variables);
+ char *name = pivot_value_to_string (d->root->name, table);
char *value = pivot_value_to_string (
- d->data_leaves[table->current_layer[i]]->name,
- table->show_values, table->show_variables);
+ d->data_leaves[table->current_layer[i]]->name, table);
printf (" %s=%s", name, value);
free (value);
free (name);
const struct pivot_dimension *d = layer_axis->dimensions[i];
fputs (i == 0 ? " " : ", ", stdout);
- pivot_value_dump (d->root->name);
+ pivot_value_dump (d->root->name, table);
fputs (" =", stdout);
- struct pivot_value **names = xnmalloc (layer_axis->label_depth,
- sizeof *names);
+ struct pivot_value **names = xnmalloc (d->n_leaves, sizeof *names);
size_t n_names = 0;
for (const struct pivot_category *c
= d->presentation_leaves[layer_indexes[i]];
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]);
+ pivot_value_dump (names[i], table);
}
free (names);
}
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,
- table->show_values, table->show_variables);
+ table, &table->axes[PIVOT_AXIS_COLUMN], column_enumeration);
for (size_t y = 0; y < table->axes[PIVOT_AXIS_COLUMN].label_depth; y++)
{
indent (indentation + 1);
printf ("-----------------------------------------------\n");
char ***row_headings = compose_headings (
- &table->axes[PIVOT_AXIS_ROW], row_enumeration,
- table->show_values, table->show_variables);
+ table, &table->axes[PIVOT_AXIS_ROW], row_enumeration);
size_t x = 0;
const size_t *pindexes[PIVOT_N_AXES]
const struct pivot_value *value = pivot_table_get (
table, dindexes);
if (value)
- pivot_value_dump (value);
+ pivot_value_dump (value, table);
}
printf ("\n");
free_headings (&table->axes[PIVOT_AXIS_ROW], row_headings);
}
- pivot_table_dump_value (table->caption, "caption", indentation);
+ pivot_table_dump_value (table->caption, "caption", table, indentation);
for (size_t i = 0; i < table->n_footnotes; i++)
{
indent (indentation);
putchar ('[');
if (f->marker)
- pivot_value_dump (f->marker);
+ pivot_value_dump (f->marker, table);
else
printf ("%zu", f->idx);
putchar (']');
- pivot_value_dump (f->content);
+ pivot_value_dump (f->content, table);
putchar ('\n');
}
free (dindexes);
- settings_set_decimal_char (old_decimal);
}
\f
static const char *
pivot_format_inner_template (struct string *out, const char *template,
char escape,
struct pivot_value **values, size_t n_values,
- enum settings_value_show show_values,
- enum settings_value_show show_variables)
+ const struct pivot_table *pt)
{
size_t args_consumed = 0;
while (*template && *template != ':')
template = consume_int (template + 1, &index);
if (index >= 1 && index <= n_values)
{
- pivot_value_format (values[index - 1], show_values,
- show_variables, out);
+ pivot_value_format (values[index - 1], pt, out);
args_consumed = MAX (args_consumed, index);
}
}
static void
pivot_format_template (struct string *out, const char *template,
const struct pivot_argument *args, size_t n_args,
- enum settings_value_show show_values,
- enum settings_value_show show_variables)
+ const struct pivot_table *pt)
{
while (*template)
{
size_t index;
template = consume_int (template + 1, &index);
if (index >= 1 && index <= n_args && args[index - 1].n > 0)
- pivot_value_format (args[index - 1].values[0],
- show_values, show_variables, out);
+ pivot_value_format (args[index - 1].values[0], pt, out);
}
else if (*template == '[')
{
int tmpl_idx = left == arg->n && *tmpl[0] != ':' ? 0 : 1;
char escape = "%^"[tmpl_idx];
size_t used = pivot_format_inner_template (
- out, tmpl[tmpl_idx], escape, values, left,
- show_values, show_variables);
+ out, tmpl[tmpl_idx], escape, values, left, pt);
if (!used || used > left)
break;
left -= used;
: global_show);
}
-/* Appends a text representation of the body of VALUE to OUT. SHOW_VALUES and
- SHOW_VARIABLES control whether variable and value labels are included.
+/* 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);
- The "body" omits subscripts and superscripts and footnotes. */
+ 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.
+
+ The "body" omits subscripts and superscripts and footnotes.
+
+ Returns true if OUT is a number (or a number plus a value label), false
+ otherwise. */
bool
pivot_value_format_body (const struct pivot_value *value,
- enum settings_value_show show_values,
- enum settings_value_show show_variables,
+ const struct pivot_table *pt,
struct string *out)
{
enum settings_value_show show;
{
case PIVOT_VALUE_NUMERIC:
show = interpret_show (settings_get_show_values (),
- show_values,
+ pt->show_values,
value->numeric.show,
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);
+ "UTF-8", format, &pt->settings);
ds_put_cstr (out, s + strspn (s, " "));
free (s);
}
case PIVOT_VALUE_STRING:
show = interpret_show (settings_get_show_values (),
- show_values,
+ pt->show_values,
value->string.show,
value->string.value_label != NULL);
if (show & SETTINGS_VALUE_SHOW_VALUE)
case PIVOT_VALUE_VARIABLE:
show = interpret_show (settings_get_show_variables (),
- show_variables,
+ pt->show_variables,
value->variable.show,
value->variable.var_label != NULL);
if (show & SETTINGS_VALUE_SHOW_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:
- pivot_format_template (out, value->template.s, value->template.args,
- value->template.n_args, show_values,
- show_variables);
+ pivot_format_template (out, value->template.local, value->template.args,
+ value->template.n_args, pt);
break;
}
return numeric;
}
-/* Appends a text representation of VALUE to OUT. SHOW_VALUES and
- SHOW_VARIABLES control whether variable and value labels are included.
+/* Appends a text representation of VALUE to OUT. Settings on
+ PT control whether variable and value labels are included.
- Subscripts and superscripts 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,
- enum settings_value_show show_values,
- enum settings_value_show show_variables,
+ const struct pivot_table *pt,
struct string *out)
{
- pivot_value_format_body ( value, show_values, show_variables, out);
+ bool numeric = pivot_value_format_body (value, pt, out);
- if (value->subscript)
- ds_put_format (out, "_%s", value->subscript);
+ const struct pivot_value_ex *ex = value->ex;
+ if (ex)
+ {
+ 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 < ex->n_footnotes; i++)
+ {
+ ds_put_byte (out, '[');
- if (value->superscript)
- ds_put_format (out, "^%s", value->superscript);
+ size_t idx = ex->footnote_indexes[i];
+ const struct pivot_footnote *f = pt->footnotes[idx];
+ pivot_footnote_format_marker (f, pt, out);
- for (size_t i = 0; i < value->n_footnotes; i++)
- {
- ds_put_byte (out, '^');
- pivot_value_format (value->footnotes[i]->marker,
- show_values, show_variables, out);
+ ds_put_byte (out, ']');
+ }
}
+
+ return numeric;
}
/* Returns a text representation of VALUE. The caller must free the string,
with free(). */
char *
pivot_value_to_string (const struct pivot_value *value,
- enum settings_value_show show_values,
- enum settings_value_show show_variables)
+ const struct pivot_table *pt)
{
struct string s = DS_EMPTY_INITIALIZER;
- pivot_value_format (value, show_values, show_variables, &s);
+ pivot_value_format (value, pt, &s);
return ds_steal_cstr (&s);
}
+char *
+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);
+}
+
+struct pivot_value *
+pivot_value_clone (const struct pivot_value *old)
+{
+ if (!old)
+ return NULL;
+
+ struct pivot_value *new = xmemdup (old, sizeof *new);
+ if (old->ex)
+ new->ex = pivot_value_ex_clone (old->ex);
+
+ switch (new->type)
+ {
+ case PIVOT_VALUE_NUMERIC:
+ new->numeric.var_name = xstrdup_if_nonnull (new->numeric.var_name);
+ new->numeric.value_label = xstrdup_if_nonnull (new->numeric.value_label);
+ break;
+
+ case PIVOT_VALUE_STRING:
+ new->string.s = xstrdup (new->string.s);
+ new->string.var_name = xstrdup_if_nonnull (new->string.var_name);
+ new->string.value_label = xstrdup_if_nonnull (new->string.value_label);
+ break;
+
+ case PIVOT_VALUE_VARIABLE:
+ new->variable.var_name = xstrdup_if_nonnull (new->variable.var_name);
+ new->variable.var_label = xstrdup_if_nonnull (new->variable.var_label);
+ break;
+
+ case PIVOT_VALUE_TEXT:
+ new->text.local = xstrdup (old->text.local);
+ new->text.c = (old->text.c == old->text.local ? new->text.local
+ : 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_if_nonnull (old->text.id));
+ break;
+
+ case PIVOT_VALUE_TEMPLATE:
+ new->template.local = xstrdup (old->template.local);
+ new->template.id = (old->template.id == old->template.local
+ ? new->template.local
+ : xstrdup (old->template.id));
+ new->template.args = xmalloc (new->template.n_args
+ * sizeof *new->template.args);
+ for (size_t i = 0; i < old->template.n_args; i++)
+ pivot_argument_copy (&new->template.args[i],
+ &old->template.args[i]);
+ break;
+
+ default:
+ NOT_REACHED ();
+ }
+ return new;
+}
+
/* Frees the data owned by V. */
void
pivot_value_destroy (struct pivot_value *value)
{
if (value)
{
- font_style_uninit (value->font_style);
- free (value->font_style);
- free (value->cell_style);
- /* Do not free the elements of footnotes because VALUE does not own
- them. */
- free (value->footnotes);
- free (value->subscript);
-
+ pivot_value_ex_destroy (value->ex);
switch (value->type)
{
case PIVOT_VALUE_NUMERIC:
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);
break;
+
+ default:
+ NOT_REACHED ();
}
free (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 (&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);
+ 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. */
void
pivot_value_set_style (struct pivot_value *value,
- const struct area_style *area)
+ const struct table_area_style *area)
+{
+ pivot_value_set_font_style (value, &area->font_style);
+ pivot_value_set_cell_style (value, &area->cell_style);
+}
+
+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 (value->font_style, &area->font_style);
+ ex->font_style = xmalloc (sizeof *ex->font_style);
+ font_style_copy (NULL, ex->font_style, font_style);
+}
- if (!value->cell_style)
- value->cell_style = xmalloc (sizeof *value->cell_style);
- *value->cell_style = area->cell_style;
+void
+pivot_value_set_cell_style (struct pivot_value *value,
+ const struct 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
+pivot_argument_copy (struct pivot_argument *dst,
+ const struct pivot_argument *src)
+{
+ *dst = (struct pivot_argument) {
+ .n = src->n,
+ .values = xmalloc (src->n * sizeof *dst->values),
+ };
+
+ for (size_t i = 0; i < src->n; i++)
+ dst->values[i] = pivot_value_clone (src->values[i]);
}
/* Frees the data owned by ARG (but not ARG itself). */
{
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,
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,
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),
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
{
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;
}
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;
}
{
struct pivot_value *value = xmalloc (sizeof *value);
*value = (struct pivot_value) {
- .type = PIVOT_VALUE_VARIABLE,
.variable = {
+ .type = PIVOT_VALUE_VARIABLE,
.var_name = xstrdup (var_get_name (variable)),
.var_label = xstrdup_if_nonempty (var_get_label (variable)),
},
pivot_value_add_footnote (struct pivot_value *v,
const struct pivot_footnote *footnote)
{
- v->footnotes = xrealloc (v->footnotes,
- (v->n_footnotes + 1) * sizeof *v->footnotes);
- v->footnotes[v->n_footnotes++] = 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 < ex->n_footnotes; i++)
+ if (ex->footnote_indexes[i] == footnote->idx)
+ return;
+
+ 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
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)
+ {
+ font_style = xmalloc (sizeof *font_style);
+ font_style_copy (NULL, font_style, old->font_style);
+ }
+
+ char **subscripts = NULL;
+ if (old->n_subscripts)
{
- const struct fmt_spec *f = pivot_table_get_format (table, rc);
- if (f)
- value->numeric.format = *f;
+ 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);
+ }
+}