X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fpivot-table.c;h=a3325a1469324332ff947444559ad6da32bbafbf;hb=08bfd1f8740fbf71e7d9671bb77ee9b234dbc9b8;hp=72a158baaad78a777935077475e529bf01285bb2;hpb=ee387adb49ae16824796827e42cdb4efb7754cb9;p=pspp diff --git a/src/output/pivot-table.c b/src/output/pivot-table.c index 72a158baaa..a3325a1469 100644 --- a/src/output/pivot-table.c +++ b/src/output/pivot-table.c @@ -18,18 +18,28 @@ #include "output/pivot-table.h" +#include +#include +#include #include #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" @@ -38,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); /* Pivot table display styling. */ @@ -128,6 +138,203 @@ pivot_table_sizing_uninit (struct pivot_table_sizing *sizing) } } +/* 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); + } + } +} + /* Axes. */ /* Returns the name of AXIS_TYPE. */ @@ -168,7 +375,8 @@ pivot_axis_iterator_next (size_t *indexes, const struct pivot_axis *axis) 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++) @@ -189,10 +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) + pivot_table_use_rc (category->dimension->table, s, + &name->numeric.format, &name->numeric.honor_small); } static void @@ -286,7 +503,7 @@ pivot_dimension_create__ (struct pivot_table *table, 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; } @@ -531,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 } }, }; @@ -552,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); + } + } } } @@ -586,120 +819,66 @@ pivot_result_class_change (const char *s_, const struct fmt_spec *format) return rc != NULL; } - -/* 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; +} + /* 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. - - Operations commonly performed on the new pivot_table: - - - If empty rows or columns should not be displayed, set ->omit_empty to - true. + 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. - - 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) -{ - struct pivot_table *table = xzalloc (sizeof *table); - 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 - 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, +pivot_table_create__ (struct pivot_value *title, const char *subtype) +{ + 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), }; - 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; } @@ -711,7 +890,7 @@ struct pivot_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")); @@ -723,23 +902,251 @@ 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; +} + +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 + ? xcalloc (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 = 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, + .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); @@ -757,9 +1164,7 @@ pivot_table_destroy (struct pivot_table *table) 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]); @@ -771,16 +1176,189 @@ pivot_table_destroy (struct pivot_table *table) 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; +} + +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, + 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. @@ -877,6 +1455,8 @@ pivot_table_put (struct pivot_table *table, const size_t *dindexes, size_t n, 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) { @@ -889,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:; } @@ -956,20 +1538,35 @@ 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. 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, @@ -984,11 +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->show_numeric_markers); - f->content = NULL; - + *f = (struct pivot_footnote) { + .idx = table->n_footnotes, + .show = true, + }; table->footnotes[table->n_footnotes++] = f; } } @@ -1033,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; } } } @@ -1068,7 +1664,7 @@ pivot_table_enumerate_axis (const struct pivot_table *table, axis->n_dimensions), 1), sizeof *enumeration); size_t *p = enumeration; - size_t *dindexes = xcalloc (table->n_dimensions, sizeof *dindexes); + size_t *dindexes = XCALLOC (table->n_dimensions, size_t); size_t *axis_indexes; PIVOT_AXIS_FOR_EACH (axis_indexes, axis) @@ -1098,6 +1694,14 @@ pivot_table_enumerate_axis (const struct pivot_table *table, memcpy (p, axis_indexes, axis->n_dimensions * sizeof *p); p += axis->n_dimensions; } + if (omit_empty && p == enumeration) + { + PIVOT_AXIS_FOR_EACH (axis_indexes, axis) + { + memcpy (p, axis_indexes, axis->n_dimensions * sizeof *p); + p += axis->n_dimensions; + } + } *p = SIZE_MAX; if (n) *n = (p - enumeration) / axis->n_dimensions; @@ -1106,9 +1710,9 @@ pivot_table_enumerate_axis (const struct pivot_table *table, 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); @@ -1130,6 +1734,27 @@ pivot_table_get_rw (struct pivot_table *table, const size_t *dindexes) 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) { @@ -1204,17 +1829,13 @@ pivot_table_assign_label_depth (struct pivot_table *table) { pivot_axis_assign_label_depth (table, PIVOT_AXIS_COLUMN, false); if (pivot_axis_assign_label_depth ( - table, PIVOT_AXIS_ROW, (table->row_labels_in_corner + table, PIVOT_AXIS_ROW, (table->look->row_labels_in_corner && !table->corner_text)) && table->axes[PIVOT_AXIS_COLUMN].label_depth == 0) table->axes[PIVOT_AXIS_COLUMN].label_depth = 1; pivot_axis_assign_label_depth (table, PIVOT_AXIS_LAYER, false); } -/* Footnotes. */ - - - static void indent (int indentation) { @@ -1223,23 +1844,23 @@ 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'); } } @@ -1255,11 +1876,12 @@ pivot_table_dump_string (const char *string, const char *name, int indentation) } 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)) @@ -1270,23 +1892,24 @@ pivot_category_dump (const struct pivot_category *c, int indentation) 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)); @@ -1308,10 +1931,9 @@ table_border_style_dump (enum pivot_border border, } 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; @@ -1338,8 +1960,7 @@ compose_headings (const struct pivot_axis *axis, 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 (""); row--; @@ -1364,44 +1985,90 @@ free_headings (const struct pivot_axis *axis, char ***headings) 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); - char buf[26]; - printf ("date: %s", ctime_r (&table->date, buf)); + + struct tm *tm = localtime (&table->date); + printf ("date: %d-%02d-%02d %d:%02d:%02d\n", tm->tm_year + 1900, + tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, + tm->tm_sec); } + indent (indentation); + printf ("sizing:\n"); + pivot_table_sizing_dump ("column", table->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) @@ -1412,12 +2079,9 @@ pivot_table_dump (const struct pivot_table *table, int indentation) 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); @@ -1439,11 +2103,10 @@ pivot_table_dump (const struct pivot_table *table, int indentation) 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]]; @@ -1454,23 +2117,22 @@ pivot_table_dump (const struct pivot_table *table, int indentation) names[n_names++] = c->name; } - for (size_t i = n_names; i-- > 0; ) + for (size_t i = n_names; i-- > 0;) { putchar (' '); - pivot_value_dump (names[i]); + 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); @@ -1489,8 +2151,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation) 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] @@ -1523,7 +2184,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation) const struct pivot_value *value = pivot_table_get ( table, dindexes); if (value) - pivot_value_dump (value); + pivot_value_dump (value, table); } printf ("\n"); @@ -1535,7 +2196,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation) 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++) { @@ -1543,16 +2204,15 @@ pivot_table_dump (const struct pivot_table *table, int indentation) 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); } static const char * @@ -1568,8 +2228,7 @@ static size_t 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 != ':') @@ -1585,8 +2244,7 @@ pivot_format_inner_template (struct string *out, const char *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); } } @@ -1617,8 +2275,7 @@ pivot_extract_inner_template (const char *template, const char **p) 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) { @@ -1632,8 +2289,7 @@ pivot_format_template (struct string *out, const char *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 == '[') { @@ -1655,8 +2311,7 @@ pivot_format_template (struct string *out, const char *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; @@ -1679,14 +2334,43 @@ interpret_show (enum settings_value_show global_show, : 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, "", strlen (""), false); + xmlParseChunk (parser, markup, strlen (markup), false); + xmlParseChunk (parser, "", strlen (""), 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. - The "body" omits subscripts and superscripts and footnotes. */ + 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; @@ -1696,13 +2380,22 @@ pivot_value_format_body (const struct pivot_value *value, { 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); } @@ -1717,7 +2410,7 @@ pivot_value_format_body (const struct pivot_value *value, 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) @@ -1741,7 +2434,7 @@ pivot_value_format_body (const struct pivot_value *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) @@ -1755,71 +2448,143 @@ 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: - 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]); + } - if (value->superscript) - ds_put_format (out, "^%s", value->superscript); + for (size_t i = 0; i < ex->n_footnotes; i++) + { + ds_put_byte (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); + 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, ']'); + } } + + 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: @@ -1848,11 +2613,16 @@ 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); break; + + default: + NOT_REACHED (); } free (value); } @@ -1862,31 +2632,58 @@ pivot_value_destroy (struct pivot_value *value) DEFAULT_STYLE for the parts of the style that VALUE doesn't override. */ void pivot_value_get_style (struct pivot_value *value, - const struct area_style *default_style, - struct area_style *area) + const struct font_style *base_font_style, + const struct cell_style *base_cell_style, + struct table_area_style *area) { - font_style_copy (&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) { - if (value->font_style) - font_style_uninit (value->font_style); + 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) +{ + 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). */ @@ -1914,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, @@ -1933,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) { @@ -1957,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, @@ -1984,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), @@ -1995,12 +2792,6 @@ pivot_value_new_text_format (const char *format, ...) return value; } -static char * -xstrdup_if_nonempty (const char *s) -{ - return s && s[0] ? xstrdup (s) : NULL; -} - /* Returns a new pivot_value that represents X. The format to use for X is unspecified. Usually the easiest way to specify @@ -2012,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; } @@ -2023,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; } @@ -2062,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), @@ -2088,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; @@ -2105,9 +2907,41 @@ void 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 @@ -2117,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); +} + +/* 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); + } +}