From: Ben Pfaff Date: Thu, 29 Oct 2020 05:30:43 +0000 (-0700) Subject: pspp-output: New command get-table-look. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;ds=sidebyside;h=308c33df7c1be1edd6b2ebdf10b901fe05903438;p=pspp pspp-output: New command get-table-look. --- diff --git a/NEWS b/NEWS index a2f3a864eb..694ec94f62 100644 --- a/NEWS +++ b/NEWS @@ -17,7 +17,11 @@ Changes from 1.4.1 to 1.5.2: The new interface provides the user with a preview of the data to be imported and interactive methods to select the desired ranges. - * The pspp-output utility has new --table-look and --nth-commands options. + * New features in pspp-output: + + - New --table-look and --nth-commands options. + + - New get-table-look command. Changes from 1.4.0 to 1.4.1: diff --git a/doc/pspp-output.texi b/doc/pspp-output.texi index 35cc600463..bc68fa067f 100644 --- a/doc/pspp-output.texi +++ b/doc/pspp-output.texi @@ -29,6 +29,8 @@ SPSS 15 and earlier versions instead use @file{.spo} files. @t{pspp-output} [@var{options}] @t{convert} @var{source} @var{destination} +@t{pspp-output} [@var{options}] @t{get-table-look} @var{source} @var{destination} + @t{pspp-output -@w{-}help} @t{pspp-output -@w{-}version} @@ -42,6 +44,7 @@ developers may find useful for debugging. * The pspp-output detect Command:: * The pspp-output dir Command:: * The pspp-output convert Command:: +* The pspp-output get-table-look Command:: * Input Selection Options:: @end menu @@ -116,6 +119,23 @@ Reads a table style from @var{file} and applies it to all of the output tables. The file should a TableLook @file{.stt} file. @end table +@node The pspp-output get-table-look Command +@section The @code{get-table-look} Command + +@display +@t{pspp-output} [@var{options}] @t{get-table-look} @var{source} @var{destination} +@end display + +Reads SPV file @var{source}, applies any selection options +(@pxref{Input Selection Options}), picks the first table from the +selected object, extracts the TableLook from that table, and writes it +to @var{destination} (typically with an @file{.stt} extension) in the +TableLook XML format. + +The user may use the TableLook file to change the style of tables in +other files, by passing it to the @option{--table-look} option on the +@code{convert} command. + @node Input Selection Options @section Input Selection Options diff --git a/src/output/pivot-table.h b/src/output/pivot-table.h index c4c9bb778f..ed8f9b7a2b 100644 --- a/src/output/pivot-table.h +++ b/src/output/pivot-table.h @@ -388,7 +388,7 @@ struct pivot_table bool show_caption; bool omit_empty; /* Omit empty rows and columns? */ size_t *current_layer; /* axis[PIVOT_AXIS_LAYER].n_dimensions elements. */ - char *table_look; + char *table_look; /* May be NULL. */ enum settings_value_show show_values; enum settings_value_show show_variables; struct fmt_spec weight_format; diff --git a/src/output/spv/spv-table-look.c b/src/output/spv/spv-table-look.c index d297c13981..3a2fb4578c 100644 --- a/src/output/spv/spv-table-look.c +++ b/src/output/spv/spv-table-look.c @@ -19,7 +19,9 @@ #include "output/spv/spv-table-look.h" #include +#include #include +#include #include #include "output/spv/structure-xml-parser.h" @@ -27,6 +29,9 @@ #include "gl/read-file.h" #include "gl/xalloc.h" +#include "gettext.h" +#define _(msgid) gettext (msgid) + static struct cell_color optional_color (int color, struct cell_color default_color) { @@ -61,55 +66,55 @@ optional_pt (double inches, int default_pt) return inches != DBL_MAX ? inches * 72.0 + .5 : default_pt; } +static const char *pivot_area_names[PIVOT_N_AREAS] = { + [PIVOT_AREA_TITLE] = "title", + [PIVOT_AREA_CAPTION] = "caption", + [PIVOT_AREA_FOOTER] = "footnotes", + [PIVOT_AREA_CORNER] = "cornerLabels", + [PIVOT_AREA_COLUMN_LABELS] = "columnLabels", + [PIVOT_AREA_ROW_LABELS] = "rowLabels", + [PIVOT_AREA_DATA] = "data", + [PIVOT_AREA_LAYERS] = "layers", +}; + static enum pivot_area pivot_area_from_name (const char *name) { - static const char *area_names[PIVOT_N_AREAS] = { - [PIVOT_AREA_TITLE] = "title", - [PIVOT_AREA_CAPTION] = "caption", - [PIVOT_AREA_FOOTER] = "footnotes", - [PIVOT_AREA_CORNER] = "cornerLabels", - [PIVOT_AREA_COLUMN_LABELS] = "columnLabels", - [PIVOT_AREA_ROW_LABELS] = "rowLabels", - [PIVOT_AREA_DATA] = "data", - [PIVOT_AREA_LAYERS] = "layers", - }; - enum pivot_area area; for (area = 0; area < PIVOT_N_AREAS; area++) - if (!strcmp (name, area_names[area])) + if (!strcmp (name, pivot_area_names[area])) break; return area; } +static const char *pivot_border_names[PIVOT_N_BORDERS] = { + [PIVOT_BORDER_TITLE] = "titleLayerSeparator", + [PIVOT_BORDER_OUTER_LEFT] = "leftOuterFrame", + [PIVOT_BORDER_OUTER_TOP] = "topOuterFrame", + [PIVOT_BORDER_OUTER_RIGHT] = "rightOuterFrame", + [PIVOT_BORDER_OUTER_BOTTOM] = "bottomOuterFrame", + [PIVOT_BORDER_INNER_LEFT] = "leftInnerFrame", + [PIVOT_BORDER_INNER_TOP] = "topInnerFrame", + [PIVOT_BORDER_INNER_RIGHT] = "rightInnerFrame", + [PIVOT_BORDER_INNER_BOTTOM] = "bottomInnerFrame", + [PIVOT_BORDER_DATA_LEFT] = "dataAreaLeft", + [PIVOT_BORDER_DATA_TOP] = "dataAreaTop", + [PIVOT_BORDER_DIM_ROW_HORZ] = "horizontalDimensionBorderRows", + [PIVOT_BORDER_DIM_ROW_VERT] = "verticalDimensionBorderRows", + [PIVOT_BORDER_DIM_COL_HORZ] = "horizontalDimensionBorderColumns", + [PIVOT_BORDER_DIM_COL_VERT] = "verticalDimensionBorderColumns", + [PIVOT_BORDER_CAT_ROW_HORZ] = "horizontalCategoryBorderRows", + [PIVOT_BORDER_CAT_ROW_VERT] = "verticalCategoryBorderRows", + [PIVOT_BORDER_CAT_COL_HORZ] = "horizontalCategoryBorderColumns", + [PIVOT_BORDER_CAT_COL_VERT] = "verticalCategoryBorderColumns", +}; + static enum pivot_border pivot_border_from_name (const char *name) { - static const char *border_names[PIVOT_N_BORDERS] = { - [PIVOT_BORDER_TITLE] = "titleLayerSeparator", - [PIVOT_BORDER_OUTER_LEFT] = "leftOuterFrame", - [PIVOT_BORDER_OUTER_TOP] = "topOuterFrame", - [PIVOT_BORDER_OUTER_RIGHT] = "rightOuterFrame", - [PIVOT_BORDER_OUTER_BOTTOM] = "bottomOuterFrame", - [PIVOT_BORDER_INNER_LEFT] = "leftInnerFrame", - [PIVOT_BORDER_INNER_TOP] = "topInnerFrame", - [PIVOT_BORDER_INNER_RIGHT] = "rightInnerFrame", - [PIVOT_BORDER_INNER_BOTTOM] = "bottomInnerFrame", - [PIVOT_BORDER_DATA_LEFT] = "dataAreaLeft", - [PIVOT_BORDER_DATA_TOP] = "dataAreaTop", - [PIVOT_BORDER_DIM_ROW_HORZ] = "horizontalDimensionBorderRows", - [PIVOT_BORDER_DIM_ROW_VERT] = "verticalDimensionBorderRows", - [PIVOT_BORDER_DIM_COL_HORZ] = "horizontalDimensionBorderColumns", - [PIVOT_BORDER_DIM_COL_VERT] = "verticalDimensionBorderColumns", - [PIVOT_BORDER_CAT_ROW_HORZ] = "horizontalCategoryBorderRows", - [PIVOT_BORDER_CAT_ROW_VERT] = "verticalCategoryBorderRows", - [PIVOT_BORDER_CAT_COL_HORZ] = "horizontalCategoryBorderColumns", - [PIVOT_BORDER_CAT_COL_VERT] = "verticalCategoryBorderColumns", - }; - enum pivot_border border; for (border = 0; border < PIVOT_N_BORDERS; border++) - if (!strcmp (name, border_names[border])) + if (!strcmp (name, pivot_border_names[border])) break; return border; } @@ -121,6 +126,8 @@ spv_table_look_decode (const struct spvsx_table_properties *in, struct spv_table_look *out = xzalloc (sizeof *out); char *error = NULL; + out->name = in->name ? xstrdup (in->name) : NULL; + const struct spvsx_general_properties *g = in->general_properties; out->omit_empty = g->hide_empty_rows != 0; out->width_ranges[TABLE_HORZ][0] = optional_pt (g->minimum_column_width, -1); @@ -299,11 +306,210 @@ spv_table_look_read (const char *filename, struct spv_table_look **outp) return error; } +static void +write_attr (xmlTextWriter *xml, const char *name, const char *value) +{ + xmlTextWriterWriteAttribute (xml, + CHAR_CAST (xmlChar *, name), + CHAR_CAST (xmlChar *, value)); +} + +static void PRINTF_FORMAT (3, 4) +write_attr_format (xmlTextWriter *xml, const char *name, + const char *format, ...) +{ + va_list args; + va_start (args, format); + char *value = xvasprintf (format, args); + va_end (args); + + write_attr (xml, name, value); + free (value); +} + +static void +write_attr_color (xmlTextWriter *xml, const char *name, + const struct cell_color *color) +{ + write_attr_format (xml, name, "#%02"PRIx8"%02"PRIx8"%02"PRIx8, + color->r, color->g, color->b); +} + +static void +write_attr_dimension (xmlTextWriter *xml, const char *name, int px) +{ + int pt = px / 96.0 * 72.0; + write_attr_format (xml, name, "%dpt", pt); +} + +static void +write_attr_bool (xmlTextWriter *xml, const char *name, bool b) +{ + write_attr (xml, name, b ? "true" : "false"); +} + +static void +start_elem (xmlTextWriter *xml, const char *name) +{ + xmlTextWriterStartElement (xml, CHAR_CAST (xmlChar *, name)); +} + +static void +end_elem (xmlTextWriter *xml) +{ + xmlTextWriterEndElement (xml); +} + +char * WARN_UNUSED_RESULT +spv_table_look_write (const char *filename, const struct spv_table_look *look) +{ + FILE *file = fopen (filename, "w"); + if (!file) + return xasprintf (_("%s: create failed (%s)"), filename, strerror (errno)); + + xmlTextWriter *xml = xmlNewTextWriter (xmlOutputBufferCreateFile ( + file, NULL)); + if (!xml) + { + fclose (file); + return xasprintf (_("%s: failed to start writing XML"), filename); + } + + xmlTextWriterSetIndent (xml, 1); + xmlTextWriterSetIndentString (xml, CHAR_CAST (xmlChar *, " ")); + + xmlTextWriterStartDocument (xml, NULL, "UTF-8", NULL); + start_elem (xml, "tableProperties"); + if (look->name) + write_attr (xml, "name", look->name); + write_attr (xml, "xmlns", "http://www.ibm.com/software/analytics/spss/xml/table-looks"); + write_attr (xml, "xmlns:vizml", "http://www.ibm.com/software/analytics/spss/xml/visualization"); + write_attr (xml, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + write_attr (xml, "xsi:schemaLocation", "http://www.ibm.com/software/analytics/spss/xml/table-looks http://www.ibm.com/software/analytics/spss/xml/table-looks/table-looks-1.4.xsd"); + + start_elem (xml, "generalProperties"); + write_attr_bool (xml, "hideEmptyRows", look->omit_empty); + const int (*wr)[2] = look->width_ranges; + write_attr_format (xml, "maximumColumnWidth", "%d", wr[TABLE_HORZ][1]); + write_attr_format (xml, "maximumRowWidth", "%d", wr[TABLE_VERT][1]); + write_attr_format (xml, "minimumColumnWidth", "%d", wr[TABLE_HORZ][0]); + write_attr_format (xml, "minimumRowWidth", "%d", wr[TABLE_VERT][0]); + write_attr (xml, "rowDimensionLabels", + look->row_labels_in_corner ? "inCorner" : "nested"); + end_elem (xml); + + start_elem (xml, "footnoteProperties"); + write_attr (xml, "markerPosition", + look->footnote_marker_superscripts ? "superscript" : "subscript"); + write_attr (xml, "numberFormat", + look->show_numeric_markers ? "numeric" : "alphabetic"); + end_elem (xml); + + start_elem (xml, "cellFormatProperties"); + for (enum pivot_area a = 0; a < PIVOT_N_AREAS; a++) + { + const struct area_style *area = &look->areas[a]; + const struct font_style *font = &area->font_style; + const struct cell_style *cell = &area->cell_style; + + start_elem (xml, pivot_area_names[a]); + if (a == PIVOT_AREA_DATA + && (!cell_color_equal (&font->fg[0], &font->fg[1]) + || !cell_color_equal (&font->bg[0], &font->bg[1]))) + { + write_attr_color (xml, "alternatingColor", &font->bg[1]); + write_attr_color (xml, "alternatingTextColor", &font->fg[1]); + } + + start_elem (xml, "vizml:style"); + write_attr_color (xml, "color", &font->fg[0]); + write_attr_color (xml, "color2", &font->bg[0]); + write_attr (xml, "font-family", font->typeface); + write_attr_format (xml, "font-size", "%dpt", font->size); + write_attr (xml, "font-weight", font->bold ? "bold" : "regular"); + write_attr (xml, "font-underline", + font->underline ? "underline" : "none"); + write_attr (xml, "labelLocationVertical", + cell->valign == TABLE_VALIGN_BOTTOM ? "negative" + : cell->valign == TABLE_VALIGN_TOP ? "positive" + : "center"); + write_attr_dimension (xml, "margin-bottom", cell->margin[TABLE_VERT][1]); + write_attr_dimension (xml, "margin-left", cell->margin[TABLE_HORZ][0]); + write_attr_dimension (xml, "margin-right", cell->margin[TABLE_HORZ][1]); + write_attr_dimension (xml, "margin-top", cell->margin[TABLE_VERT][0]); + write_attr (xml, "textAlignment", + cell->halign == TABLE_HALIGN_LEFT ? "left" + : cell->halign == TABLE_HALIGN_RIGHT ? "right" + : cell->halign == TABLE_HALIGN_CENTER ? "center" + : cell->halign == TABLE_HALIGN_DECIMAL ? "decimal" + : "mixed"); + if (cell->halign == TABLE_HALIGN_DECIMAL) + write_attr_dimension (xml, "decimal-offset", cell->decimal_offset); + end_elem (xml); + + end_elem (xml); + } + end_elem (xml); + + start_elem (xml, "borderProperties"); + for (enum pivot_border b = 0; b < PIVOT_N_BORDERS; b++) + { + const struct table_border_style *border = &look->borders[b]; + + start_elem (xml, pivot_border_names[b]); + + static const char *table_stroke_names[TABLE_N_STROKES] = + { + [TABLE_STROKE_NONE] = "none", + [TABLE_STROKE_SOLID] = "solid", + [TABLE_STROKE_DASHED] = "dashed", + [TABLE_STROKE_THICK] = "thick", + [TABLE_STROKE_THIN] = "thin", + [TABLE_STROKE_DOUBLE] = "double", + }; + write_attr (xml, "borderStyleType", table_stroke_names[border->stroke]); + write_attr_color (xml, "color", &border->color); + end_elem (xml); + } + end_elem (xml); + + start_elem (xml, "printingProperties"); + write_attr_bool (xml, "printAllLayers", look->print_all_layers); + write_attr_bool (xml, "rescaleLongTableToFitPage", look->shrink_to_length); + write_attr_bool (xml, "rescaleWideTableToFitPage", look->shrink_to_width); + write_attr_format (xml, "windowOrphanLines", "%zu", look->n_orphan_lines); + if (look->continuation && look->continuation[0] + && (look->top_continuation || look->bottom_continuation)) + { + write_attr_format (xml, "continuationText", look->continuation); + write_attr_bool (xml, "continuationTextAtTop", look->top_continuation); + write_attr_bool (xml, "continuationTextAtBottom", + look->bottom_continuation); + } + end_elem (xml); + + xmlTextWriterEndDocument (xml); + + xmlFreeTextWriter (xml); + + fflush (file); + bool ok = !ferror (file); + if (fclose (file) == EOF) + ok = false; + + if (!ok) + return xasprintf (_("%s: error writing file (%s)"), + filename, strerror (errno)); + + return NULL; +} + void spv_table_look_destroy (struct spv_table_look *look) { if (look) { + free (look->name); for (size_t i = 0; i < PIVOT_N_AREAS; i++) area_style_uninit (&look->areas[i]); free (look->continuation); @@ -315,6 +521,10 @@ void spv_table_look_install (const struct spv_table_look *look, struct pivot_table *table) { + free (table->table_look); + if (look->name) + table->table_look = xstrdup (look->name); + table->omit_empty = look->omit_empty; for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++) @@ -343,3 +553,37 @@ spv_table_look_install (const struct spv_table_look *look, table->continuation = xstrdup (look->continuation); table->n_orphan_lines = look->n_orphan_lines; } + +struct spv_table_look * +spv_table_look_get (const struct pivot_table *table) +{ + struct spv_table_look *look = xzalloc (sizeof *look); + + look->name = table->table_look ? xstrdup (table->table_look) : NULL; + + look->omit_empty = table->omit_empty; + + for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++) + for (int i = 0; i < 2; i++) + look->width_ranges[axis][i] = table->sizing[axis].range[i]; + look->row_labels_in_corner = table->row_labels_in_corner; + + look->footnote_marker_superscripts = table->footnote_marker_superscripts; + look->show_numeric_markers = table->show_numeric_markers; + + for (size_t i = 0; i < PIVOT_N_AREAS; i++) + area_style_copy (NULL, &look->areas[i], &table->areas[i]); + for (size_t i = 0; i < PIVOT_N_BORDERS; i++) + look->borders[i] = table->borders[i]; + + look->print_all_layers = table->print_all_layers; + look->paginate_layers = table->paginate_layers; + look->shrink_to_width = table->shrink_to_fit[TABLE_HORZ]; + look->shrink_to_length = table->shrink_to_fit[TABLE_VERT]; + look->top_continuation = table->top_continuation; + look->bottom_continuation = table->bottom_continuation; + look->continuation = xstrdup (table->continuation); + look->n_orphan_lines = table->n_orphan_lines; + + return look; +} diff --git a/src/output/spv/spv-table-look.h b/src/output/spv/spv-table-look.h index 3e817571dd..9867595021 100644 --- a/src/output/spv/spv-table-look.h +++ b/src/output/spv/spv-table-look.h @@ -33,6 +33,8 @@ struct spvsx_table_properties; struct spv_table_look { + char *name; /* May be null. */ + /* General properties. */ bool omit_empty; int width_ranges[TABLE_N_AXES][2]; /* In 1/96" units. */ @@ -63,8 +65,11 @@ char *spv_table_look_decode (const struct spvsx_table_properties *, WARN_UNUSED_RESULT; char *spv_table_look_read (const char *, struct spv_table_look **) WARN_UNUSED_RESULT; +char *spv_table_look_write (const char *, const struct spv_table_look *) + WARN_UNUSED_RESULT; void spv_table_look_install (const struct spv_table_look *, struct pivot_table *); +struct spv_table_look *spv_table_look_get (const struct pivot_table *); #endif /* output/spv/spv-table-look.h */ diff --git a/utilities/pspp-output.1 b/utilities/pspp-output.1 index 598b6f9ddc..252fec9a00 100644 --- a/utilities/pspp-output.1 +++ b/utilities/pspp-output.1 @@ -16,6 +16,8 @@ pspp\-output \- convert and operate on SPSS viewer (SPV) files .br \fBpspp\-output \fR[\fIoptions\fR] \fBconvert\fR \fIsource destination\fR .br +\fBpspp\-output \fR[\fIoptions\fR] \fBget\-table\-look\fR \fIsource destination\fR +.br \fBpspp\-output \-\-help\fR | \fB\-h\fR .br \fBpspp\-output \-\-version\fR | \fB\-v\fR @@ -83,6 +85,18 @@ write the output as best it can, even with errors. .IP \fB\-\-table\-look=\fIfile\fR Reads a table style from \fIfile\fR and applies it to all of the output tables. The file should a TableLook \fB.stt\fR file. +.SS The \fBget\-table\-look\fR command +When invoked as \fBpspp\-output get\-table\-look \fIsource +destination\fR, \fBpspp\-output\fR reads SPV file \fIsource\fR, +applies any selection options (as described under \fBInput Selection +Options\fR below), picks the first table from the selected object, +extracts the TableLook from that table, and writes it to +\fIdestination\fR (typically with an \fB.stt\fR extension) in the +TableLook XML format. +.PP +The user may use the TableLook file to change the style of tables in +other files, by passing it to the \fB\-\-table\-look\fR option on the +\fBconvert\fR command. .SS "Input Selection Options" The \fBdir\fR and \fBconvert\fR commands, by default, operate on all of the objects in the source SPV file, except for objects that are not diff --git a/utilities/pspp-output.c b/utilities/pspp-output.c index 8362ac57ff..4b9710a020 100644 --- a/utilities/pspp-output.c +++ b/utilities/pspp-output.c @@ -325,6 +325,45 @@ run_convert (int argc UNUSED, char **argv) } } +static const struct pivot_table * +get_first_table (const struct spv_reader *spv) +{ + struct spv_item **items; + size_t n_items; + spv_select (spv, criteria, n_criteria, &items, &n_items); + + for (size_t i = 0; i < n_items; i++) + if (spv_item_is_table (items[i])) + { + free (items); + return spv_item_get_table (items[i]); + } + + free (items); + return NULL; +} + +static void +run_get_table_look (int argc UNUSED, char **argv) +{ + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + const struct pivot_table *table = get_first_table (spv); + if (!table) + error (1, 0, "%s: no tables found", argv[1]); + + struct spv_table_look *look = spv_table_look_get (table); + err = spv_table_look_write (argv[2], look); + if (err) + error (1, 0, "%s", err); + spv_table_look_destroy (look); + + spv_close (spv); +} + static void run_dump (int argc UNUSED, char **argv) { @@ -672,6 +711,7 @@ static const struct command commands[] = { "detect", 1, 1, run_detect }, { "dir", 1, 1, run_directory }, { "convert", 2, 2, run_convert }, + { "get-table-look", 2, 2, run_get_table_look }, /* Undocumented commands. */ { "dump", 1, 1, run_dump }, @@ -1053,6 +1093,7 @@ The following commands are available:\n\ detect FILE Detect whether FILE is an SPV file.\n\ dir FILE List tables and other items in FILE.\n\ convert SOURCE DEST Convert .spv SOURCE to DEST.\n\ + get-table-look SOURCE DEST Copies first selected TableLook into DEST\n\ \n\ Input selection options for \"dir\" and \"convert\":\n\ --select=CLASS... include only some kinds of objects\n\