X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fpivot-table.c;h=93dc74c2d86e8299cbbe3e03e2d6de8a790ef799;hb=9449e8429a4bc1368c322457384d69e22f8d0bb2;hp=9e45f244e061f6e162873e5c7a2f71e9ee304cf8;hpb=21f20b8cb6460fd5ac9db7fde038bc00cfa80831;p=pspp diff --git a/src/output/pivot-table.c b/src/output/pivot-table.c index 9e45f244e0..93dc74c2d8 100644 --- a/src/output/pivot-table.c +++ b/src/output/pivot-table.c @@ -18,9 +18,13 @@ #include "output/pivot-table.h" +#include +#include +#include #include #include "data/data-out.h" +#include "data/dictionary.h" #include "data/settings.h" #include "data/value.h" #include "data/variable.h" @@ -45,8 +49,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. */ @@ -394,17 +398,19 @@ pivot_axis_iterator_next (size_t *indexes, const struct pivot_axis *axis) static void pivot_category_set_rc (struct pivot_category *category, const char *s) { - const struct fmt_spec *format = pivot_table_get_format ( - category->dimension->table, s); - if (format) - category->format = *format; + if (!s) + return; + + pivot_table_use_rc (category->dimension->table, s, + &category->format, &category->honor_small); /* Ensure that the category itself, in addition to the cells within it, takes the format. (It's kind of rare for a category to have a numeric format though.) */ struct pivot_value *name = category->name; if (name->type == PIVOT_VALUE_NUMERIC && !name->numeric.format.w) - name->numeric.format = format ? *format : *settings_get_format (); + pivot_table_use_rc (category->dimension->table, s, + &name->numeric.format, &name->numeric.honor_small); } static void @@ -743,11 +749,11 @@ struct result_class /* Formats for most of the result classes. */ static struct result_class result_classes[] = { - { PIVOT_RC_INTEGER, { FMT_F, 40, 0 } }, - { PIVOT_RC_PERCENT, { FMT_PCT, 40, 1 } }, - { PIVOT_RC_CORRELATION, { FMT_F, 40, 3 } }, - { PIVOT_RC_SIGNIFICANCE, { FMT_F, 40, 3 } }, - { PIVOT_RC_RESIDUAL, { FMT_F, 40, 2 } }, + { PIVOT_RC_INTEGER, { .type = FMT_F, .w = 40, .d = 0 } }, + { PIVOT_RC_PERCENT, { .type = FMT_PCT, .w = 40, .d = 1 } }, + { PIVOT_RC_CORRELATION, { .type = FMT_F, .w = 40, .d = 3 } }, + { PIVOT_RC_SIGNIFICANCE, { .type = FMT_F, .w = 40, .d = 3 } }, + { PIVOT_RC_RESIDUAL, { .type = FMT_F, .w = 40, .d = 2 } }, { PIVOT_RC_COUNT, { 0, 0, 0 } }, { PIVOT_RC_OTHER, { 0, 0, 0 } }, }; @@ -764,19 +770,35 @@ pivot_result_class_find (const char *s) return NULL; } -static const struct fmt_spec * -pivot_table_get_format (const struct pivot_table *table, const char *s) +static void +pivot_table_use_rc (const struct pivot_table *table, const char *s, + struct fmt_spec *format, bool *honor_small) { - if (!s) - return NULL; - else if (!strcmp (s, PIVOT_RC_OTHER)) - return settings_get_format (); - else if (!strcmp (s, PIVOT_RC_COUNT) && !overridden_count_format) - return &table->weight_format; - else + if (s) { - const struct result_class *rc = pivot_result_class_find (s); - return rc ? &rc->format : NULL; + if (!strcmp (s, PIVOT_RC_OTHER)) + { + *format = *settings_get_format (); + *honor_small = true; + } + else if (!strcmp (s, PIVOT_RC_COUNT) && !overridden_count_format) + { + *format = table->weight_format; + *honor_small = false; + } + else + { + const struct result_class *rc = pivot_result_class_find (s); + if (rc) + { + *format = rc->format; + *honor_small = false; + } + else + { + printf ("unknown class %s\n", s); + } + } } } @@ -844,18 +866,20 @@ pivot_table_create (const char *title) struct pivot_table * pivot_table_create__ (struct pivot_value *title, const char *subtype) { - struct pivot_table *table = xzalloc (sizeof *table); - table->ref_cnt = 1; - table->show_title = true; - table->show_caption = true; - table->weight_format = (struct fmt_spec) { FMT_F, 40, 0 }; - table->title = title; - table->subtype = subtype ? pivot_value_new_text (subtype) : NULL; - table->command_c = output_get_command_name (); - table->look = pivot_table_look_ref (pivot_table_look_get_default ()); - - hmap_init (&table->cells); - + struct pivot_table *table = xmalloc (sizeof *table); + *table = (struct pivot_table) { + .ref_cnt = 1, + .show_title = true, + .show_caption = true, + .weight_format = (struct fmt_spec) { .type = FMT_F, .w = 40 }, + .title = title, + .subtype = subtype ? pivot_value_new_text (subtype) : NULL, + .command_c = xstrdup_if_nonempty (output_get_command_name ()), + .look = pivot_table_look_ref (pivot_table_look_get_default ()), + .settings = fmt_settings_copy (settings_get_fmt_settings ()), + .small = settings_get_small (), + .cells = HMAP_INITIALIZER (table->cells), + }; return table; } @@ -890,12 +914,6 @@ pivot_table_ref (const struct pivot_table *table_) return table; } -static char * -xstrdup_if_nonnull (const char *s) -{ - return s ? xstrdup (s) : NULL; -} - static struct pivot_table_sizing clone_sizing (const struct pivot_table_sizing *s) { @@ -951,7 +969,7 @@ clone_category (struct pivot_category *old, .extra_depth = old->extra_depth, .subs = (old->n_subs - ? xzalloc (old->n_subs * sizeof *new->subs) + ? xcalloc (old->n_subs, sizeof *new->subs) : NULL), .n_subs = old->n_subs, .allocated_subs = old->n_subs, @@ -967,7 +985,10 @@ clone_category (struct pivot_category *old, if (pivot_category_is_leaf (old)) { + assert (new->data_index < new_dimension->n_leaves); new->dimension->data_leaves[new->data_index] = new; + + assert (new->presentation_index < new_dimension->n_leaves); new->dimension->presentation_leaves[new->presentation_index] = new; } @@ -986,9 +1007,9 @@ clone_dimension (struct pivot_dimension *old, struct pivot_table *new_pt) .axis_type = old->axis_type, .level = old->level, .top_index = old->top_index, - .data_leaves = xzalloc (old->n_leaves * sizeof *new->data_leaves), - .presentation_leaves = xzalloc (old->n_leaves - * sizeof *new->presentation_leaves), + .data_leaves = xcalloc (old->n_leaves , sizeof *new->data_leaves), + .presentation_leaves = xcalloc (old->n_leaves + , sizeof *new->presentation_leaves), .n_leaves = old->n_leaves, .allocated_leaves = old->n_leaves, .hide_all_labels = old->hide_all_labels, @@ -1047,16 +1068,8 @@ pivot_table_unshare (struct pivot_table *old) [TABLE_VERT] = clone_sizing (&old->sizing[TABLE_VERT]), }, - .epoch = old->epoch, - .decimal = old->decimal, + .settings = fmt_settings_copy (&old->settings), .grouping = old->grouping, - .ccs = { - [0] = xstrdup_if_nonnull (old->ccs[0]), - [1] = xstrdup_if_nonnull (old->ccs[1]), - [2] = xstrdup_if_nonnull (old->ccs[2]), - [3] = xstrdup_if_nonnull (old->ccs[3]), - [4] = xstrdup_if_nonnull (old->ccs[4]), - }, .small = old->small, .command_local = xstrdup_if_nonnull (old->command_local), @@ -1134,8 +1147,7 @@ pivot_table_unref (struct pivot_table *table) for (int i = 0; i < TABLE_N_AXES; i++) pivot_table_sizing_uninit (&table->sizing[i]); - 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); @@ -1180,6 +1192,43 @@ pivot_table_is_shared (const struct pivot_table *table) return table->ref_cnt > 1; } +static void +pivot_table_set_value__ (struct pivot_value **dstp, struct pivot_value *src) +{ + pivot_value_destroy (*dstp); + *dstp = src; +} + +/* Changes the title of TABLE to TITLE. Takes ownership of TITLE. */ +void +pivot_table_set_title (struct pivot_table *table, struct pivot_value *title) +{ + pivot_table_set_value__ (&table->title, title); +} + +/* Changes the subtype of TABLE to SUBTYPE. Takes ownership of SUBTYPE. */ +void +pivot_table_set_subtype (struct pivot_table *table, struct pivot_value *subtype) +{ + pivot_table_set_value__ (&table->subtype, subtype); +} + +/* Changes the corner text of TABLE to CORNER_TEXT. Takes ownership of + CORNER_TEXT. */ +void +pivot_table_set_corner_text (struct pivot_table *table, + struct pivot_value *corner_text) +{ + pivot_table_set_value__ (&table->corner_text, corner_text); +} + +/* Changes the caption of TABLE to CAPTION. Takes ownership of CAPTION. */ +void +pivot_table_set_caption (struct pivot_table *table, struct pivot_value *caption) +{ + pivot_table_set_value__ (&table->caption, caption); +} + /* Swaps axes A and B in TABLE. */ void pivot_table_swap_axes (struct pivot_table *table, @@ -1250,7 +1299,6 @@ pivot_table_move_dimension (struct pivot_table *table, return; } - /* Update the current layer, if necessary. If we're moving within the layer axis, preserve the current layer. */ if (dim->axis_type == PIVOT_AXIS_LAYER) @@ -1396,8 +1444,9 @@ pivot_table_insert_cell (struct pivot_table *table, const size_t *dindexes) } /* Puts VALUE in the cell in TABLE whose indexes are given by the N indexes in - DINDEXES. N must be the number of dimensions in TABLE. Takes ownership of - VALUE. + DINDEXES. The order of the indexes is the same as the order in which the + dimensions were created. N must be the number of dimensions in TABLE. + Takes ownership of VALUE. If VALUE is a numeric value without a specified format, this function checks each of the categories designated by DINDEXES[] and takes the format from @@ -1422,11 +1471,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:; } @@ -1489,15 +1540,30 @@ pivot_table_create_footnote (struct pivot_table *table, NULL, content); } -static struct pivot_value * -pivot_make_default_footnote_marker (int idx, bool show_numeric_markers) -{ - char text[INT_BUFSIZE_BOUND (size_t)]; - if (show_numeric_markers) - snprintf (text, sizeof text, "%d", idx + 1); +void +pivot_footnote_format_marker (const struct pivot_footnote *f, + const struct pivot_table *pt, + struct string *s) +{ + if (f->marker) + pivot_value_format_body (f->marker, pt, s); + else if (pt->look->show_numeric_markers) + ds_put_format (s, "%zu", f->idx + 1); else - str_format_26adic (idx + 1, false, text, sizeof text); - return pivot_value_new_user_text (text, -1); + { + char text[INT_BUFSIZE_BOUND (size_t)]; + str_format_26adic (f->idx + 1, false, text, sizeof text); + ds_put_cstr (s, text); + } +} + +char * +pivot_footnote_marker_string (const struct pivot_footnote *f, + const struct pivot_table *pt) +{ + struct string s = DS_EMPTY_INITIALIZER; + pivot_footnote_format_marker (f, pt, &s); + return ds_steal_cstr (&s); } /* Creates or modifies a footnote in TABLE with 0-based number IDX (and creates @@ -1517,12 +1583,10 @@ pivot_table_create_footnote__ (struct pivot_table *table, size_t idx, while (idx >= table->n_footnotes) { struct pivot_footnote *f = xmalloc (sizeof *f); - f->idx = table->n_footnotes; - f->marker = pivot_make_default_footnote_marker ( - f->idx, table->look->show_numeric_markers); - f->content = NULL; - f->show = true; - + *f = (struct pivot_footnote) { + .idx = table->n_footnotes, + .show = true, + }; table->footnotes[table->n_footnotes++] = f; } } @@ -1567,8 +1631,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; } } } @@ -1782,22 +1846,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_defaults (value); + 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'); } } @@ -1813,11 +1878,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)) @@ -1828,18 +1894,19 @@ 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 @@ -1911,6 +1978,8 @@ compose_headings (const struct pivot_table *pt, static void free_headings (const struct pivot_axis *axis, char ***headings) { + if (!headings) + return; for (size_t i = 0; i < axis->label_depth; i++) { for (size_t j = 0; j < axis->extent; j++) @@ -1964,12 +2033,8 @@ pivot_table_dump (const struct pivot_table *table, int indentation) pivot_table_assign_label_depth (CONST_CAST (struct pivot_table *, table)); - int old_decimal = settings_get_decimal_char (FMT_COMMA); - if (table->decimal == '.' || table->decimal == ',') - settings_set_decimal_char (table->decimal); - - pivot_table_dump_value (table->title, "title", indentation); - pivot_table_dump_value (table->subtype, "subtype", 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); @@ -2004,7 +2069,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation) 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, size_t); @@ -2042,7 +2107,7 @@ 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 (d->n_leaves, sizeof *names); @@ -2059,7 +2124,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation) for (size_t i = n_names; i-- > 0;) { putchar (' '); - pivot_value_dump (names[i]); + pivot_value_dump (names[i], table); } free (names); } @@ -2070,21 +2135,30 @@ pivot_table_dump (const struct pivot_table *table, int indentation) size_t *row_enumeration = pivot_table_enumerate_axis ( table, PIVOT_AXIS_ROW, layer_indexes, table->look->omit_empty, NULL); + /* Print column headings. + + Ordinarily the test for nonnull 'column_headings' would be + unnecessary, because 'column_headings' is null only if the axis's + label_depth is 0, but there is a special case for the column axis only + in pivot_table_assign_label_depth(). */ char ***column_headings = compose_headings ( table, &table->axes[PIVOT_AXIS_COLUMN], column_enumeration); - for (size_t y = 0; y < table->axes[PIVOT_AXIS_COLUMN].label_depth; y++) + if (column_headings) { - indent (indentation + 1); - for (size_t x = 0; x < table->axes[PIVOT_AXIS_COLUMN].extent; x++) + for (size_t y = 0; y < table->axes[PIVOT_AXIS_COLUMN].label_depth; y++) { - if (x) - fputs ("; ", stdout); - if (column_headings[y][x]) - fputs (column_headings[y][x], stdout); + indent (indentation + 1); + for (size_t x = 0; x < table->axes[PIVOT_AXIS_COLUMN].extent; x++) + { + if (x) + fputs ("; ", stdout); + if (column_headings && column_headings[y] && column_headings[y][x]) + fputs (column_headings[y][x], stdout); + } + putchar ('\n'); } - putchar ('\n'); + free_headings (&table->axes[PIVOT_AXIS_COLUMN], column_headings); } - free_headings (&table->axes[PIVOT_AXIS_COLUMN], column_headings); indent (indentation + 1); printf ("-----------------------------------------------\n"); @@ -2123,7 +2197,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"); @@ -2135,7 +2209,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++) { @@ -2143,16 +2217,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 * @@ -2274,8 +2347,42 @@ interpret_show (enum settings_value_show global_show, : global_show); } -/* Appends a text representation of the body of VALUE to OUT. Settings on - PT 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); +} + +static const struct pivot_table pivot_value_format_defaults = { + .show_values = SETTINGS_VALUE_SHOW_DEFAULT, + .show_variables = SETTINGS_VALUE_SHOW_DEFAULT, + .settings = FMT_SETTINGS_INIT, +}; + +/* Appends a text representation of the body of VALUE to OUT. Settings on PT + control whether variable and value labels are included (pass NULL for PT to + get default formatting in the absence of a pivot table). The "body" omits subscripts and superscripts and footnotes. @@ -2283,9 +2390,10 @@ interpret_show (enum settings_value_show global_show, otherwise. */ bool pivot_value_format_body (const struct pivot_value *value, - const struct pivot_table *pt, + const struct pivot_table *pt_, struct string *out) { + const struct pivot_table *pt = pt_ ? pt_ : &pivot_value_format_defaults; enum settings_value_show show; bool numeric = false; @@ -2298,8 +2406,17 @@ pivot_value_format_body (const struct pivot_value *value, value->numeric.value_label != NULL); if (show & SETTINGS_VALUE_SHOW_VALUE) { + const struct fmt_spec *f = &value->numeric.format; + const struct fmt_spec *format + = (f->type == FMT_F + && value->numeric.honor_small + && value->numeric.x != 0 + && fabs (value->numeric.x) < pt->small + ? &(struct fmt_spec) { .type = FMT_E, .w = 40, .d = f->d } + : f); + char *s = data_out (&(union value) { .f = value->numeric.x }, - "UTF-8", &value->numeric.format); + "UTF-8", format, &pt->settings); ds_put_cstr (out, s + strspn (s, " ")); free (s); } @@ -2352,7 +2469,10 @@ pivot_value_format_body (const struct pivot_value *value, break; case PIVOT_VALUE_TEXT: - ds_put_cstr (out, value->text.local); + if (value->ex && value->ex->font_style && value->ex->font_style->markup) + get_text_from_markup (value->text.local, out); + else + ds_put_cstr (out, value->text.local); break; case PIVOT_VALUE_TEMPLATE: @@ -2364,37 +2484,50 @@ pivot_value_format_body (const struct pivot_value *value, return numeric; } -/* Appends a text representation of VALUE to OUT. Settings on - PT 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 (pass NULL for PT to get + default formatting in the absence of a pivot table). - Subscripts and footnotes are included. */ -void + Subscripts and footnotes are included. + + Returns true if OUT is a number (or a number plus a value label), false + otherwise. */ +bool pivot_value_format (const struct pivot_value *value, - const struct pivot_table *pt, + const struct pivot_table *pt_, struct string *out) { - pivot_value_format_body (value, pt, out); + const struct pivot_table *pt = pt_ ? pt_ : &pivot_value_format_defaults; + bool numeric = pivot_value_format_body (value, pt, out); - if (value->n_subscripts) + const struct pivot_value_ex *ex = value->ex; + if (ex) { - for (size_t i = 0; i < value->n_subscripts; i++) - ds_put_format (out, "%c%s", i ? ',' : '_', value->subscripts[i]); - } + if (ex->n_subscripts) + { + for (size_t i = 0; i < ex->n_subscripts; i++) + ds_put_format (out, "%c%s", i ? ',' : '_', ex->subscripts[i]); + } - for (size_t i = 0; i < value->n_footnotes; i++) - { - ds_put_byte (out, '['); + for (size_t i = 0; i < ex->n_footnotes; i++) + { + ds_put_byte (out, '['); - size_t idx = value->footnote_indexes[i]; - const struct pivot_footnote *f = pt->footnotes[idx]; - pivot_value_format (f->marker, pt, out); + size_t idx = ex->footnote_indexes[i]; + const struct pivot_footnote *f = pt->footnotes[idx]; + pivot_footnote_format_marker (f, pt, out); - ds_put_byte (out, ']'); + ds_put_byte (out, ']'); + } } + + return numeric; } /* Returns a text representation of VALUE. The caller must free the string, - with free(). */ + with free(). Settings on PT control whether variable and value labels are + included (pass NULL for PT to get default formatting in the absence of a + pivot table). */ char * pivot_value_to_string (const struct pivot_value *value, const struct pivot_table *pt) @@ -2404,16 +2537,6 @@ pivot_value_to_string (const struct pivot_value *value, 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, - }; - return pivot_value_to_string (value, &pt); -} - struct pivot_value * pivot_value_clone (const struct pivot_value *old) { @@ -2421,22 +2544,8 @@ pivot_value_clone (const struct pivot_value *old) return NULL; struct pivot_value *new = xmemdup (old, sizeof *new); - if (old->font_style) - { - new->font_style = xmalloc (sizeof *new->font_style); - font_style_copy (NULL, new->font_style, old->font_style); - } - if (old->cell_style) - new->font_style = xmemdup (old->font_style, sizeof *new->font_style); - if (old->n_subscripts) - { - new->subscripts = xnmalloc (old->n_subscripts, sizeof *new->subscripts); - for (size_t i = 0; i < old->n_subscripts; i++) - new->subscripts[i] = xstrdup (old->subscripts[i]); - } - if (old->n_footnotes) - new->footnote_indexes = xmemdup ( - old->footnote_indexes, old->n_footnotes * sizeof *new->footnote_indexes); + if (old->ex) + new->ex = pivot_value_ex_clone (old->ex); switch (new->type) { @@ -2459,10 +2568,10 @@ pivot_value_clone (const struct pivot_value *old) case PIVOT_VALUE_TEXT: new->text.local = xstrdup (old->text.local); new->text.c = (old->text.c == old->text.local ? new->text.local - : xstrdup (old->text.c)); + : xstrdup_if_nonnull (old->text.c)); new->text.id = (old->text.id == old->text.local ? new->text.local : old->text.id == old->text.c ? new->text.c - : xstrdup (old->text.id)); + : xstrdup_if_nonnull (old->text.id)); break; case PIVOT_VALUE_TEMPLATE: @@ -2489,15 +2598,7 @@ pivot_value_destroy (struct pivot_value *value) { if (value) { - font_style_uninit (value->font_style); - free (value->font_style); - free (value->cell_style); - free (value->footnote_indexes); - - for (size_t i = 0; i < value->n_subscripts; i++) - free (value->subscripts[i]); - free (value->subscripts); - + pivot_value_ex_destroy (value->ex); switch (value->type) { case PIVOT_VALUE_NUMERIC: @@ -2549,12 +2650,10 @@ pivot_value_get_style (struct pivot_value *value, const struct cell_style *base_cell_style, struct table_area_style *area) { - font_style_copy (NULL, &area->font_style, (value->font_style - ? value->font_style - : base_font_style)); - area->cell_style = *(value->cell_style - ? value->cell_style - : base_cell_style); + const struct pivot_value_ex *ex = pivot_value_ex (value); + font_style_copy (NULL, &area->font_style, + ex->font_style ? ex->font_style : base_font_style); + area->cell_style = *(ex->cell_style ? ex->cell_style : base_cell_style); } /* Copies AREA into VALUE's style. */ @@ -2570,20 +2669,22 @@ void pivot_value_set_font_style (struct pivot_value *value, const struct font_style *font_style) { - if (value->font_style) - font_style_uninit (value->font_style); + struct pivot_value_ex *ex = pivot_value_ex_rw (value); + if (ex->font_style) + font_style_uninit (ex->font_style); else - value->font_style = xmalloc (sizeof *value->font_style); - font_style_copy (NULL, value->font_style, font_style); + ex->font_style = xmalloc (sizeof *ex->font_style); + font_style_copy (NULL, ex->font_style, font_style); } void pivot_value_set_cell_style (struct pivot_value *value, const struct cell_style *cell_style) { - if (!value->cell_style) - value->cell_style = xmalloc (sizeof *value->cell_style); - *value->cell_style = *cell_style; + struct pivot_value_ex *ex = pivot_value_ex_rw (value); + if (!ex->cell_style) + ex->cell_style = xmalloc (sizeof *ex->cell_style); + *ex->cell_style = *cell_style; } void @@ -2624,8 +2725,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, @@ -2643,9 +2744,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) { @@ -2667,8 +2768,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, @@ -2679,7 +2780,7 @@ pivot_value_new_text (const char *text) } /* Same as pivot_value_new_text() but its argument is a printf()-like format - string. */ + string. The format string should generally be enclosed in N_(). */ struct pivot_value * PRINTF_FORMAT (1, 2) pivot_value_new_text_format (const char *format, ...) { @@ -2694,8 +2795,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), @@ -2716,8 +2817,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; } @@ -2727,7 +2830,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; } @@ -2766,7 +2869,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), @@ -2792,13 +2895,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; @@ -2809,15 +2921,41 @@ void pivot_value_add_footnote (struct pivot_value *v, const struct pivot_footnote *footnote) { + struct pivot_value_ex *ex = pivot_value_ex_rw (v); + /* Some legacy tables include numerous duplicate footnotes. Suppress them. */ - for (size_t i = 0; i < v->n_footnotes; i++) - if (v->footnote_indexes[i] == footnote->idx) + for (size_t i = 0; i < ex->n_footnotes; i++) + if (ex->footnote_indexes[i] == footnote->idx) return; - v->footnote_indexes = xrealloc ( - v->footnote_indexes, (v->n_footnotes + 1) * sizeof *v->footnote_indexes); - v->footnote_indexes[v->n_footnotes++] = footnote->idx; + ex->footnote_indexes = xrealloc ( + ex->footnote_indexes, + (ex->n_footnotes + 1) * sizeof *ex->footnote_indexes); + ex->footnote_indexes[ex->n_footnotes++] = footnote->idx; + pivot_value_sort_footnotes (v); +} + +static int +compare_footnote_indexes (const void *a_, const void *b_) +{ + const size_t *ap = a_; + const size_t *bp = b_; + size_t a = *ap; + size_t b = *bp; + return a < b ? -1 : a > b; +} + +/* Sorts the footnote references in V in the standard ascending order. + + This is only necessary if code adds (plural) footnotes to a pivot_value by + itself, because pivot_value_add_footnote() does it automatically. */ +void +pivot_value_sort_footnotes (struct pivot_value *v) +{ + if (v->ex && v->ex->n_footnotes > 1) + qsort (v->ex->footnote_indexes, v->ex->n_footnotes, + sizeof *v->ex->footnote_indexes, compare_footnote_indexes); } /* If VALUE is a numeric value, and RC is a result class such as @@ -2827,10 +2965,406 @@ 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) + { + 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); + } +} + +/* pivot_splits */ + +struct pivot_splits_value + { + struct hmap_node hmap_node; + union value value; + int leaf; + }; + +struct pivot_splits_var + { + struct pivot_dimension *dimension; + const struct variable *var; + int width; + struct hmap values; + }; + +struct pivot_splits_dup + { + struct hmap_node hmap_node; + union value *values; + }; + +struct pivot_splits + { + struct pivot_splits_var *vars; + size_t n; + char *encoding; + + struct hmap dups; + + size_t dindexes[MAX_SPLITS]; + + int warnings_left; + }; + +/* Adds a dimension for each layered split file variable in DICT to PT on AXIS. + These dimensions should be the last dimensions added to PT (the + pivot_splits_put*() functions rely on this). Returns a new pivot_splits + structure if any dimensions were added, otherwise a null pointer. + + See the large comment on split file handling in pivot-table.h for more + information. */ +struct pivot_splits * +pivot_splits_create (struct pivot_table *pt, + enum pivot_axis_type axis, + const struct dictionary *dict) +{ + if (dict_get_split_type (dict) != SPLIT_LAYERED) + return NULL; + + size_t n = dict_get_n_splits (dict); + assert (n <= MAX_SPLITS); + + const struct variable *const *vars = dict_get_split_vars (dict); + struct pivot_splits_var *psvars = xnmalloc (n, sizeof *psvars); + for (size_t i = n - 1; i < n; i--) + { + const struct variable *var = vars[i]; + struct pivot_splits_var *psvar = &psvars[i]; + + struct pivot_dimension *d = pivot_dimension_create__ ( + pt, axis, pivot_value_new_variable (var)); + d->root->show_label = true; + + *psvar = (struct pivot_splits_var) { + .width = var_get_width (var), + .values = HMAP_INITIALIZER (psvar->values), + .dimension = d, + .var = var, + }; + } + + struct pivot_splits *ps = xmalloc (sizeof *ps); + *ps = (struct pivot_splits) { + .vars = psvars, + .n = n, + .encoding = xstrdup (dict_get_encoding (dict)), + .dups = HMAP_INITIALIZER (ps->dups), + .dindexes = { [0] = SIZE_MAX }, + .warnings_left = 5, + }; + return ps; +} + +/* Destroys PS. */ +void +pivot_splits_destroy (struct pivot_splits *ps) +{ + if (!ps) + return; + + if (ps->warnings_left < 0) + msg (SW, ngettext ("Suppressed %d additional warning about duplicate " + "split values.", + "Suppressed %d additional warnings about duplicate " + "split values.", -ps->warnings_left), + -ps->warnings_left); + + struct pivot_splits_dup *dup, *next_dup; + HMAP_FOR_EACH_SAFE (dup, next_dup, struct pivot_splits_dup, hmap_node, + &ps->dups) + { + for (size_t i = 0; i < ps->n; i++) + value_destroy (&dup->values[i], ps->vars[i].width); + free (dup->values); + free (dup); + } + hmap_destroy (&ps->dups); + + for (size_t i = 0; i < ps->n; i++) + { + struct pivot_splits_var *psvar = &ps->vars[i]; + struct pivot_splits_value *psval, *next; + HMAP_FOR_EACH_SAFE (psval, next, struct pivot_splits_value, hmap_node, + &psvar->values) + { + value_destroy (&psval->value, psvar->width); + hmap_delete (&psvar->values, &psval->hmap_node); + free (psval); + } + hmap_destroy (&psvar->values); + } + free (ps->vars); + + free (ps->encoding); + free (ps); +} + +static struct pivot_splits_value * +pivot_splits_value_find (struct pivot_splits_var *psvar, + const union value *value) +{ + struct pivot_splits_value *psval; + HMAP_FOR_EACH_WITH_HASH (psval, struct pivot_splits_value, hmap_node, + value_hash (value, psvar->width, 0), &psvar->values) + if (value_equal (&psval->value, value, psvar->width)) + return psval; + return NULL; +} + +static bool +pivot_splits_find_dup (struct pivot_splits *ps, const struct ccase *example) +{ + unsigned int hash = 0; + for (size_t i = 0; i < ps->n; i++) + { + struct pivot_splits_var *psvar = &ps->vars[i]; + const union value *value = case_data (example, psvar->var); + hash = value_hash (value, psvar->width, hash); + } + struct pivot_splits_dup *dup; + HMAP_FOR_EACH_WITH_HASH (dup, struct pivot_splits_dup, hmap_node, hash, + &ps->dups) + { + bool equal = true; + for (size_t i = 0; i < ps->n && equal; i++) + { + struct pivot_splits_var *psvar = &ps->vars[i]; + const union value *value = case_data (example, psvar->var); + equal = value_equal (value, &dup->values[i], psvar->width); + } + if (equal) + return true; + } + + union value *values = xmalloc (ps->n * sizeof *values); + for (size_t i = 0; i < ps->n; i++) + { + struct pivot_splits_var *psvar = &ps->vars[i]; + const union value *value = case_data (example, psvar->var); + value_clone (&values[i], value, psvar->width); + } + + dup = xmalloc (sizeof *dup); + dup->values = values; + hmap_insert (&ps->dups, &dup->hmap_node, hash); + return false; +} + +/* Begins adding data for a new split file group to the pivot table associated + with PS. EXAMPLE should be a case from the new split file group. + + This is a no-op if PS is NULL. + + See the large comment on split file handling in pivot-table.h for more + information. */ +void +pivot_splits_new_split (struct pivot_splits *ps, const struct ccase *example) +{ + if (!ps) + return; + + for (size_t i = 0; i < ps->n; i++) + { + struct pivot_splits_var *psvar = &ps->vars[i]; + const union value *value = case_data (example, psvar->var); + struct pivot_splits_value *psval = pivot_splits_value_find (psvar, value); + if (!psval) + { + psval = xmalloc (sizeof *psval); + hmap_insert (&psvar->values, &psval->hmap_node, + value_hash (value, psvar->width, 0)); + value_clone (&psval->value, value, psvar->width); + psval->leaf = pivot_category_create_leaf ( + psvar->dimension->root, + pivot_value_new_var_value (psvar->var, value)); + } + + ps->dindexes[i] = psval->leaf; + } + + if (pivot_splits_find_dup (ps, example)) { - const struct fmt_spec *f = pivot_table_get_format (table, rc); - if (f) - value->numeric.format = *f; + if (ps->warnings_left-- > 0) + { + struct string s = DS_EMPTY_INITIALIZER; + for (size_t i = 0; i < ps->n; i++) + { + if (i > 0) + ds_put_cstr (&s, ", "); + + struct pivot_splits_var *psvar = &ps->vars[i]; + const union value *value = case_data (example, psvar->var); + ds_put_format (&s, "%s = ", var_get_name (psvar->var)); + + char *s2 = data_out (value, ps->encoding, + var_get_print_format (psvar->var), + settings_get_fmt_settings ()); + ds_put_cstr (&s, s2 + strspn (s2, " ")); + free (s2); + } + msg (SW, _("When SPLIT FILE is in effect, the input data must be " + "sorted by the split variables (for example, using SORT " + "CASES), but multiple runs of cases with the same split " + "values were found separated by cases with different " + "values. Each run will be analyzed separately. The " + "duplicate split values are: %s"), ds_cstr (&s)); + ds_destroy (&s); + } + + struct pivot_splits_var *psvar = &ps->vars[0]; + const union value *value = case_data (example, psvar->var); + ps->dindexes[0] = pivot_category_create_leaf ( + psvar->dimension->root, + pivot_value_new_var_value (psvar->var, value)); } } +static size_t +pivot_splits_get_dindexes (const struct pivot_splits *ps, size_t *dindexes) +{ + if (!ps) + return 0; + + assert (ps->dindexes[0] != SIZE_MAX); + for (size_t i = 0; i < ps->n; i++) + dindexes[ps->n - i - 1] = ps->dindexes[i]; + return ps->n; +} + +/* Puts VALUE in the cell in TABLE with index IDX1. TABLE must have 1 + dimension plus the split file dimensions from PS (if nonnull). Takes + ownership of VALUE. + + See the large comment on split file handling in pivot-table.h for more + information. */ +void +pivot_splits_put1 (struct pivot_splits *ps, struct pivot_table *table, + size_t idx1, struct pivot_value *value) +{ + size_t dindexes[1 + MAX_SPLITS]; + size_t *p = dindexes; + *p++ = idx1; + p += pivot_splits_get_dindexes (ps, p); + pivot_table_put (table, dindexes, p - dindexes, value); +} + +/* Puts VALUE in the cell in TABLE with index (IDX1, IDX2). TABLE must have 2 + dimensions plus the split file dimensions from PS (if nonnull). Takes + ownership of VALUE. + + See the large comment on split file handling in pivot-table.h for more + information. */ +void +pivot_splits_put2 (struct pivot_splits *ps, struct pivot_table *table, + size_t idx1, size_t idx2, struct pivot_value *value) +{ + size_t dindexes[2 + MAX_SPLITS]; + size_t *p = dindexes; + *p++ = idx1; + *p++ = idx2; + p += pivot_splits_get_dindexes (ps, p); + pivot_table_put (table, dindexes, p - dindexes, value); +} + +/* Puts VALUE in the cell in TABLE with index (IDX1, IDX2, IDX3). TABLE must + have 3 dimensions plus the split file dimensions from PS (if nonnull). + Takes ownership of VALUE. + + See the large comment on split file handling in pivot-table.h for more + information. */ +void +pivot_splits_put3 (struct pivot_splits *ps, struct pivot_table *table, + size_t idx1, size_t idx2, size_t idx3, + struct pivot_value *value) +{ + size_t dindexes[3 + MAX_SPLITS]; + size_t *p = dindexes; + *p++ = idx1; + *p++ = idx2; + *p++ = idx3; + p += pivot_splits_get_dindexes (ps, p); + pivot_table_put (table, dindexes, p - dindexes, value); +} + +/* Puts VALUE in the cell in TABLE with index (IDX1, IDX2, IDX3, IDX4). TABLE + must have 4 dimensions plus the split file dimensions from PS (if nonnull). + Takes ownership of VALUE. + + See the large comment on split file handling in pivot-table.h for more + information. */ +void +pivot_splits_put4 (struct pivot_splits *ps, struct pivot_table *table, + size_t idx1, size_t idx2, size_t idx3, size_t idx4, + struct pivot_value *value) +{ + size_t dindexes[4 + MAX_SPLITS]; + size_t *p = dindexes; + *p++ = idx1; + *p++ = idx2; + *p++ = idx3; + *p++ = idx4; + p += pivot_splits_get_dindexes (ps, p); + pivot_table_put (table, dindexes, p - dindexes, value); +}