#include "output/pivot-table.h"
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <math.h>
#include <stdlib.h>
#include "data/data-out.h"
+#include "data/dictionary.h"
#include "data/settings.h"
#include "data/value.h"
#include "data/variable.h"
#define _(msgid) gettext (msgid)
#define N_(msgid) msgid
-static const struct fmt_spec *pivot_table_get_format (
- const struct pivot_table *, const char *s);
+static void pivot_table_use_rc (const struct pivot_table *, const char *s,
+ struct fmt_spec *, bool *honor_small);
\f
/* Pivot table display styling. */
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
/* Formats for most of the result classes. */
static struct result_class result_classes[] =
{
- { PIVOT_RC_INTEGER, { FMT_F, 40, 0 } },
- { PIVOT_RC_PERCENT, { FMT_PCT, 40, 1 } },
- { PIVOT_RC_CORRELATION, { FMT_F, 40, 3 } },
- { PIVOT_RC_SIGNIFICANCE, { FMT_F, 40, 3 } },
- { PIVOT_RC_RESIDUAL, { FMT_F, 40, 2 } },
+ { PIVOT_RC_INTEGER, { .type = FMT_F, .w = 40, .d = 0 } },
+ { PIVOT_RC_PERCENT, { .type = FMT_PCT, .w = 40, .d = 1 } },
+ { PIVOT_RC_CORRELATION, { .type = FMT_F, .w = 40, .d = 3 } },
+ { PIVOT_RC_SIGNIFICANCE, { .type = FMT_F, .w = 40, .d = 3 } },
+ { PIVOT_RC_RESIDUAL, { .type = FMT_F, .w = 40, .d = 2 } },
{ PIVOT_RC_COUNT, { 0, 0, 0 } },
{ PIVOT_RC_OTHER, { 0, 0, 0 } },
};
return NULL;
}
-static const struct fmt_spec *
-pivot_table_get_format (const struct pivot_table *table, const char *s)
+static void
+pivot_table_use_rc (const struct pivot_table *table, const char *s,
+ struct fmt_spec *format, bool *honor_small)
{
- if (!s)
- return NULL;
- else if (!strcmp (s, PIVOT_RC_OTHER))
- return settings_get_format ();
- else if (!strcmp (s, PIVOT_RC_COUNT) && !overridden_count_format)
- return &table->weight_format;
- else
+ if (s)
{
- const struct result_class *rc = pivot_result_class_find (s);
- return rc ? &rc->format : NULL;
+ if (!strcmp (s, PIVOT_RC_OTHER))
+ {
+ *format = *settings_get_format ();
+ *honor_small = true;
+ }
+ else if (!strcmp (s, PIVOT_RC_COUNT) && !overridden_count_format)
+ {
+ *format = table->weight_format;
+ *honor_small = false;
+ }
+ else
+ {
+ const struct result_class *rc = pivot_result_class_find (s);
+ if (rc)
+ {
+ *format = rc->format;
+ *honor_small = false;
+ }
+ else
+ {
+ printf ("unknown class %s\n", s);
+ }
+ }
}
}
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;
}
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)
{
.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,
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;
}
.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,
[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),
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);
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,
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)
}
/* 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
if (c->format.w)
{
value->numeric.format = c->format;
+ value->numeric.honor_small = c->honor_small;
goto done;
}
}
}
value->numeric.format = *settings_get_format ();
+ value->numeric.honor_small = true;
done:;
}
NULL, content);
}
-static struct pivot_value *
-pivot_make_default_footnote_marker (int idx, bool show_numeric_markers)
-{
- char text[INT_BUFSIZE_BOUND (size_t)];
- if (show_numeric_markers)
- snprintf (text, sizeof text, "%d", idx + 1);
+void
+pivot_footnote_format_marker (const struct pivot_footnote *f,
+ const struct pivot_table *pt,
+ struct string *s)
+{
+ if (f->marker)
+ pivot_value_format_body (f->marker, pt, s);
+ else if (pt->look->show_numeric_markers)
+ ds_put_format (s, "%zu", f->idx + 1);
else
- str_format_26adic (idx + 1, false, text, sizeof text);
- return pivot_value_new_user_text (text, -1);
+ {
+ char text[INT_BUFSIZE_BOUND (size_t)];
+ str_format_26adic (f->idx + 1, false, text, sizeof text);
+ ds_put_cstr (s, text);
+ }
+}
+
+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
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;
}
}
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;
}
}
}
}
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');
}
}
}
static void
-pivot_category_dump (const struct pivot_category *c, int indentation)
+pivot_category_dump (const struct pivot_category *c,
+ const struct pivot_table *pt, int indentation)
{
indent (indentation);
printf ("%s \"", pivot_category_is_leaf (c) ? "leaf" : "group");
- pivot_value_dump (c->name);
+ pivot_value_dump (c->name, pt);
printf ("\" ");
if (pivot_category_is_leaf (c))
printf ("\n");
for (size_t i = 0; i < c->n_subs; i++)
- pivot_category_dump (c->subs[i], indentation + 1);
+ pivot_category_dump (c->subs[i], pt, indentation + 1);
}
}
void
-pivot_dimension_dump (const struct pivot_dimension *d, int indentation)
+pivot_dimension_dump (const struct pivot_dimension *d,
+ const struct pivot_table *pt, int indentation)
{
indent (indentation);
printf ("%s dimension %zu (where 0=innermost), label_depth=%d:\n",
pivot_axis_type_to_string (d->axis_type), d->level, d->label_depth);
- pivot_category_dump (d->root, indentation + 1);
+ pivot_category_dump (d->root, pt, indentation + 1);
}
static void
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++)
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);
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);
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);
for (size_t i = n_names; i-- > 0;)
{
putchar (' ');
- pivot_value_dump (names[i]);
+ pivot_value_dump (names[i], table);
}
free (names);
}
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");
const struct pivot_value *value = pivot_table_get (
table, dindexes);
if (value)
- pivot_value_dump (value);
+ pivot_value_dump (value, table);
}
printf ("\n");
free_headings (&table->axes[PIVOT_AXIS_ROW], row_headings);
}
- pivot_table_dump_value (table->caption, "caption", indentation);
+ pivot_table_dump_value (table->caption, "caption", table, indentation);
for (size_t i = 0; i < table->n_footnotes; i++)
{
indent (indentation);
putchar ('[');
if (f->marker)
- pivot_value_dump (f->marker);
+ pivot_value_dump (f->marker, table);
else
printf ("%zu", f->idx);
putchar (']');
- pivot_value_dump (f->content);
+ pivot_value_dump (f->content, table);
putchar ('\n');
}
free (dindexes);
- settings_set_decimal_char (old_decimal);
}
\f
static const char *
: 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, "<xml>", strlen ("<xml>"), false);
+ xmlParseChunk (parser, markup, strlen (markup), false);
+ xmlParseChunk (parser, "</xml>", strlen ("</xml>"), true);
+
+ if (parser->wellFormed)
+ {
+ xmlChar *s = xmlNodeGetContent (xmlDocGetRootElement (parser->myDoc));
+ ds_put_cstr (out, CHAR_CAST (char *, s));
+ xmlFree (s);
+ }
+ else
+ ds_put_cstr (out, markup);
+ xmlFreeDoc (parser->myDoc);
+ xmlFreeParserCtxt (parser);
+}
+
+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.
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;
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);
}
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:
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)
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)
{
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)
{
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:
{
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:
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. */
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
{
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,
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)
{
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,
}
/* 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, ...)
{
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),
{
struct pivot_value *value = xmalloc (sizeof *value);
*value = (struct pivot_value) {
- .type = PIVOT_VALUE_NUMERIC,
- .numeric = { .x = x, },
+ .numeric = {
+ .type = PIVOT_VALUE_NUMERIC,
+ .x = x
+ },
};
return value;
}
pivot_value_new_integer (double x)
{
struct pivot_value *value = pivot_value_new_number (x);
- value->numeric.format = (struct fmt_spec) { FMT_F, 40, 0 };
+ value->numeric.format = (struct fmt_spec) { .type = FMT_F, .w = 40 };
return value;
}
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),
/* 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;
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
const char *rc)
{
if (value->type == PIVOT_VALUE_NUMERIC)
+ pivot_table_use_rc (table, rc,
+ &value->numeric.format, &value->numeric.honor_small);
+}
+\f
+/* pivot_value_ex. */
+
+struct pivot_value_ex *
+pivot_value_ex_rw (struct pivot_value *value)
+{
+ if (!value->ex)
+ value->ex = xzalloc (sizeof *value->ex);
+ return value->ex;
+}
+
+struct pivot_value_ex *
+pivot_value_ex_clone (const struct pivot_value_ex *old)
+{
+ struct font_style *font_style = NULL;
+ if (old->font_style)
+ {
+ font_style = xmalloc (sizeof *font_style);
+ font_style_copy (NULL, font_style, old->font_style);
+ }
+
+ char **subscripts = NULL;
+ if (old->n_subscripts)
+ {
+ 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);
+ }
+}
+\f
+/* 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
+ {
+ struct pivot_splits_var *vars;
+ size_t n;
+ char *encoding;
+
+ 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)),
+ .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);
+
+ for (size_t i = 0; i < ps->n; i++)
{
- const struct fmt_spec *f = pivot_table_get_format (table, rc);
- if (f)
- value->numeric.format = *f;
+ 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;
+}
+
+/* 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;
+
+ size_t n_changed = 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);
+ 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));
+ }
+
+ if (ps->dindexes[i] != psval->leaf)
+ {
+ ps->dindexes[i] = psval->leaf;
+ n_changed++;
+ }
+ }
+
+ if (!n_changed)
+ {
+ 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);
+}