pspp-output: New option --table-look.
[pspp] / src / output / spv / spv-table-look.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2017, 2018, 2020 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "output/spv/spv-table-look.h"
20
21 #include <errno.h>
22 #include <libxml/xmlreader.h>
23 #include <string.h>
24
25 #include "output/spv/structure-xml-parser.h"
26
27 #include "gl/read-file.h"
28 #include "gl/xalloc.h"
29
30 static struct cell_color
31 optional_color (int color, struct cell_color default_color)
32 {
33   return (color >= 0
34           ? (struct cell_color) CELL_COLOR (color >> 16, color >> 8, color)
35           : default_color);
36 }
37
38 static int
39 optional_length (const char *s, int default_length)
40 {
41   /* There is usually a "pt" suffix.  We ignore it. */
42   int length;
43   return s && sscanf (s, "%d", &length) == 1 ? length : default_length;
44 }
45
46 static int
47 optional_px (double inches, int default_px)
48 {
49   return inches != DBL_MAX ? inches * 96.0 : default_px;
50 }
51
52 static int
53 optional_int (int x, int default_value)
54 {
55   return x != INT_MIN ? x : default_value;
56 }
57
58 static int
59 optional_pt (double inches, int default_pt)
60 {
61   return inches != DBL_MAX ? inches * 72.0 + .5 : default_pt;
62 }
63
64 static enum pivot_area
65 pivot_area_from_name (const char *name)
66 {
67   static const char *area_names[PIVOT_N_AREAS] = {
68     [PIVOT_AREA_TITLE] = "title",
69     [PIVOT_AREA_CAPTION] = "caption",
70     [PIVOT_AREA_FOOTER] = "footnotes",
71     [PIVOT_AREA_CORNER] = "cornerLabels",
72     [PIVOT_AREA_COLUMN_LABELS] = "columnLabels",
73     [PIVOT_AREA_ROW_LABELS] = "rowLabels",
74     [PIVOT_AREA_DATA] = "data",
75     [PIVOT_AREA_LAYERS] = "layers",
76   };
77
78   enum pivot_area area;
79   for (area = 0; area < PIVOT_N_AREAS; area++)
80     if (!strcmp (name, area_names[area]))
81       break;
82   return area;
83 }
84
85 static enum pivot_border
86 pivot_border_from_name (const char *name)
87 {
88   static const char *border_names[PIVOT_N_BORDERS] = {
89     [PIVOT_BORDER_TITLE] = "titleLayerSeparator",
90     [PIVOT_BORDER_OUTER_LEFT] = "leftOuterFrame",
91     [PIVOT_BORDER_OUTER_TOP] = "topOuterFrame",
92     [PIVOT_BORDER_OUTER_RIGHT] = "rightOuterFrame",
93     [PIVOT_BORDER_OUTER_BOTTOM] = "bottomOuterFrame",
94     [PIVOT_BORDER_INNER_LEFT] = "leftInnerFrame",
95     [PIVOT_BORDER_INNER_TOP] = "topInnerFrame",
96     [PIVOT_BORDER_INNER_RIGHT] = "rightInnerFrame",
97     [PIVOT_BORDER_INNER_BOTTOM] = "bottomInnerFrame",
98     [PIVOT_BORDER_DATA_LEFT] = "dataAreaLeft",
99     [PIVOT_BORDER_DATA_TOP] = "dataAreaTop",
100     [PIVOT_BORDER_DIM_ROW_HORZ] = "horizontalDimensionBorderRows",
101     [PIVOT_BORDER_DIM_ROW_VERT] = "verticalDimensionBorderRows",
102     [PIVOT_BORDER_DIM_COL_HORZ] = "horizontalDimensionBorderColumns",
103     [PIVOT_BORDER_DIM_COL_VERT] = "verticalDimensionBorderColumns",
104     [PIVOT_BORDER_CAT_ROW_HORZ] = "horizontalCategoryBorderRows",
105     [PIVOT_BORDER_CAT_ROW_VERT] = "verticalCategoryBorderRows",
106     [PIVOT_BORDER_CAT_COL_HORZ] = "horizontalCategoryBorderColumns",
107     [PIVOT_BORDER_CAT_COL_VERT] = "verticalCategoryBorderColumns",
108   };
109
110   enum pivot_border border;
111   for (border = 0; border < PIVOT_N_BORDERS; border++)
112     if (!strcmp (name, border_names[border]))
113       break;
114   return border;
115 }
116
117 char * WARN_UNUSED_RESULT
118 spv_table_look_decode (const struct spvsx_table_properties *in,
119                        struct spv_table_look **outp)
120 {
121   struct spv_table_look *out = xzalloc (sizeof *out);
122   char *error = NULL;
123
124   const struct spvsx_general_properties *g = in->general_properties;
125   out->omit_empty = g->hide_empty_rows != 0;
126   out->width_ranges[TABLE_HORZ][0] = optional_pt (g->minimum_column_width, -1);
127   out->width_ranges[TABLE_HORZ][1] = optional_pt (g->maximum_column_width, -1);
128   out->width_ranges[TABLE_VERT][0] = optional_pt (g->minimum_row_width, -1);
129   out->width_ranges[TABLE_VERT][1] = optional_pt (g->maximum_row_width, -1);
130   out->row_labels_in_corner
131     = g->row_dimension_labels != SPVSX_ROW_DIMENSION_LABELS_NESTED;
132
133   const struct spvsx_footnote_properties *f = in->footnote_properties;
134   out->footnote_marker_superscripts
135     = (f->marker_position != SPVSX_MARKER_POSITION_SUBSCRIPT);
136   out->show_numeric_markers
137     = (f->number_format == SPVSX_NUMBER_FORMAT_NUMERIC);
138
139   for (int i = 0; i < PIVOT_N_AREAS; i++)
140     area_style_copy (NULL, &out->areas[i], pivot_area_get_default_style (i));
141
142   const struct spvsx_cell_format_properties *cfp = in->cell_format_properties;
143   for (size_t i = 0; i < cfp->n_cell_style; i++)
144     {
145       const struct spvsx_cell_style *c = cfp->cell_style[i];
146       const char *name = CHAR_CAST (const char *, c->node_.raw->name);
147       enum pivot_area area = pivot_area_from_name (name);
148       if (area == PIVOT_N_AREAS)
149         {
150           error = xasprintf ("unknown area \"%s\" in cellFormatProperties",
151                              name);
152           goto error;
153         }
154
155       struct area_style *a = &out->areas[area];
156       const struct spvsx_style *s = c->style;
157       if (s->font_weight)
158         a->font_style.bold = s->font_weight == SPVSX_FONT_WEIGHT_BOLD;
159       if (s->font_style)
160         a->font_style.italic = s->font_style == SPVSX_FONT_STYLE_ITALIC;
161       a->font_style.underline = false;
162       if (s->color >= 0)
163         a->font_style.fg[0] = optional_color (
164           s->color, (struct cell_color) CELL_COLOR_BLACK);
165       if (c->alternating_text_color >= 0 || s->color >= 0)
166         a->font_style.fg[1] = optional_color (c->alternating_text_color,
167                                               a->font_style.fg[0]);
168       if (s->color2 >= 0)
169         a->font_style.bg[0] = optional_color (
170           s->color2, (struct cell_color) CELL_COLOR_WHITE);
171       if (c->alternating_color >= 0 || s->color2 >= 0)
172         a->font_style.bg[1] = optional_color (c->alternating_color,
173                                               a->font_style.bg[0]);
174       if (s->font_family)
175         {
176           free (a->font_style.typeface);
177           a->font_style.typeface = xstrdup (s->font_family);
178         }
179
180       if (s->font_size)
181         a->font_style.size = optional_length (s->font_size, 0);
182
183       if (s->text_alignment)
184         a->cell_style.halign
185           = (s->text_alignment == SPVSX_TEXT_ALIGNMENT_LEFT
186              ? TABLE_HALIGN_LEFT
187              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_RIGHT
188              ? TABLE_HALIGN_RIGHT
189              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_CENTER
190              ? TABLE_HALIGN_CENTER
191              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_DECIMAL
192              ? TABLE_HALIGN_DECIMAL
193              : TABLE_HALIGN_MIXED);
194       if (s->label_location_vertical)
195         a->cell_style.valign
196           = (s->label_location_vertical == SPVSX_LABEL_LOCATION_VERTICAL_NEGATIVE
197              ? TABLE_VALIGN_BOTTOM
198              : s->label_location_vertical == SPVSX_LABEL_LOCATION_VERTICAL_POSITIVE
199              ? TABLE_VALIGN_TOP
200              : TABLE_VALIGN_CENTER);
201
202       if (s->decimal_offset != DBL_MAX)
203         a->cell_style.decimal_offset = optional_px (s->decimal_offset, 0);
204
205       if (s->margin_left != DBL_MAX)
206         a->cell_style.margin[TABLE_HORZ][0] = optional_px (s->margin_left, 8);
207       if (s->margin_right != DBL_MAX)
208         a->cell_style.margin[TABLE_HORZ][1] = optional_px (s->margin_right,
209                                                            11);
210       if (s->margin_top != DBL_MAX)
211         a->cell_style.margin[TABLE_VERT][0] = optional_px (s->margin_top, 1);
212       if (s->margin_bottom != DBL_MAX)
213         a->cell_style.margin[TABLE_VERT][1] = optional_px (s->margin_bottom,
214                                                            1);
215     }
216
217   for (int i = 0; i < PIVOT_N_BORDERS; i++)
218     pivot_border_get_default_style (i, &out->borders[i]);
219
220   const struct spvsx_border_properties *bp = in->border_properties;
221   for (size_t i = 0; i < bp->n_border_style; i++)
222     {
223       const struct spvsx_border_style *bin = bp->border_style[i];
224       const char *name = CHAR_CAST (const char *, bin->node_.raw->name);
225       enum pivot_border border = pivot_border_from_name (name);
226       if (border == PIVOT_N_BORDERS)
227         {
228           error = xasprintf ("unknown border \"%s\" parsing borderProperties",
229                              name);
230           goto error;
231         }
232
233       struct table_border_style *bout = &out->borders[border];
234       bout->stroke
235         = (bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_NONE
236            ? TABLE_STROKE_NONE
237            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_DASHED
238            ? TABLE_STROKE_DASHED
239            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_THICK
240            ? TABLE_STROKE_THICK
241            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_THIN
242            ? TABLE_STROKE_THIN
243            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_DOUBLE
244            ? TABLE_STROKE_DOUBLE
245            : TABLE_STROKE_SOLID);
246       bout->color = optional_color (bin->color,
247                                     (struct cell_color) CELL_COLOR_BLACK);
248     }
249
250   const struct spvsx_printing_properties *pp = in->printing_properties;
251   out->print_all_layers = pp->print_all_layers > 0;
252   out->paginate_layers = pp->print_each_layer_on_separate_page > 0;
253   out->shrink_to_width = pp->rescale_wide_table_to_fit_page > 0;
254   out->shrink_to_length = pp->rescale_long_table_to_fit_page > 0;
255   out->top_continuation = pp->continuation_text_at_top > 0;
256   out->bottom_continuation = pp->continuation_text_at_bottom > 0;
257   out->continuation = xstrdup (pp->continuation_text
258                                ? pp->continuation_text : "(cont.)");
259   out->n_orphan_lines = optional_int (pp->window_orphan_lines, 2);
260
261   *outp = out;
262   return NULL;
263
264 error:
265   spv_table_look_destroy (out);
266   *outp = NULL;
267   return error;
268 }
269
270 char * WARN_UNUSED_RESULT
271 spv_table_look_read (const char *filename, struct spv_table_look **outp)
272 {
273   *outp = NULL;
274
275   size_t length;
276   char *file = read_file (filename, 0, &length);
277   if (!file)
278     return xasprintf ("%s: failed to read file (%s)",
279                       filename, strerror (errno));
280
281   xmlDoc *doc = xmlReadMemory (file, length, NULL, NULL, XML_PARSE_NOBLANKS);
282   free (file);
283   if (!doc)
284     return xasprintf ("%s: failed to parse XML", filename);
285
286   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
287   struct spvsx_table_properties *tp;
288   spvsx_parse_table_properties (&ctx, xmlDocGetRootElement (doc), &tp);
289   char *error = spvxml_context_finish (&ctx, &tp->node_);
290
291   if (!error)
292     error = spv_table_look_decode (tp, outp);
293
294   spvsx_free_table_properties (tp);
295   xmlFreeDoc (doc);
296
297   return error;
298 }
299
300 void
301 spv_table_look_destroy (struct spv_table_look *look)
302 {
303   if (look)
304     {
305       for (size_t i = 0; i < PIVOT_N_AREAS; i++)
306         area_style_uninit (&look->areas[i]);
307       free (look->continuation);
308       free (look);
309     }
310 }
311
312 void
313 spv_table_look_install (const struct spv_table_look *look,
314                         struct pivot_table *table)
315 {
316   table->omit_empty = look->omit_empty;
317
318   for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
319     for (int i = 0; i < 2; i++)
320       if (look->width_ranges[axis][i] > 0)
321         table->sizing[axis].range[i] = look->width_ranges[axis][i];
322   table->row_labels_in_corner = look->row_labels_in_corner;
323
324   table->footnote_marker_superscripts = look->footnote_marker_superscripts;
325   table->show_numeric_markers = look->show_numeric_markers;
326
327   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
328     {
329       area_style_uninit (&table->areas[i]);
330       area_style_copy (NULL, &table->areas[i], &look->areas[i]);
331     }
332   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
333     table->borders[i] = look->borders[i];
334
335   table->print_all_layers = look->print_all_layers;
336   table->paginate_layers = look->paginate_layers;
337   table->shrink_to_fit[TABLE_HORZ] = look->shrink_to_width;
338   table->shrink_to_fit[TABLE_VERT] = look->shrink_to_length;
339   table->top_continuation = look->top_continuation;
340   table->bottom_continuation = look->bottom_continuation;
341   table->continuation = xstrdup (look->continuation);
342   table->n_orphan_lines = look->n_orphan_lines;
343 }