d297c13981cbaa22bdb2ba4adf1d374a9ed00bb9
[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       if (s->font_underline)
162         a->font_style.underline
163           = s->font_underline == SPVSX_FONT_UNDERLINE_UNDERLINE;
164       if (s->color >= 0)
165         a->font_style.fg[0] = optional_color (
166           s->color, (struct cell_color) CELL_COLOR_BLACK);
167       if (c->alternating_text_color >= 0 || s->color >= 0)
168         a->font_style.fg[1] = optional_color (c->alternating_text_color,
169                                               a->font_style.fg[0]);
170       if (s->color2 >= 0)
171         a->font_style.bg[0] = optional_color (
172           s->color2, (struct cell_color) CELL_COLOR_WHITE);
173       if (c->alternating_color >= 0 || s->color2 >= 0)
174         a->font_style.bg[1] = optional_color (c->alternating_color,
175                                               a->font_style.bg[0]);
176       if (s->font_family)
177         {
178           free (a->font_style.typeface);
179           a->font_style.typeface = xstrdup (s->font_family);
180         }
181
182       if (s->font_size)
183         a->font_style.size = optional_length (s->font_size, 0);
184
185       if (s->text_alignment)
186         a->cell_style.halign
187           = (s->text_alignment == SPVSX_TEXT_ALIGNMENT_LEFT
188              ? TABLE_HALIGN_LEFT
189              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_RIGHT
190              ? TABLE_HALIGN_RIGHT
191              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_CENTER
192              ? TABLE_HALIGN_CENTER
193              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_DECIMAL
194              ? TABLE_HALIGN_DECIMAL
195              : TABLE_HALIGN_MIXED);
196       if (s->label_location_vertical)
197         a->cell_style.valign
198           = (s->label_location_vertical == SPVSX_LABEL_LOCATION_VERTICAL_NEGATIVE
199              ? TABLE_VALIGN_BOTTOM
200              : s->label_location_vertical == SPVSX_LABEL_LOCATION_VERTICAL_POSITIVE
201              ? TABLE_VALIGN_TOP
202              : TABLE_VALIGN_CENTER);
203
204       if (s->decimal_offset != DBL_MAX)
205         a->cell_style.decimal_offset = optional_px (s->decimal_offset, 0);
206
207       if (s->margin_left != DBL_MAX)
208         a->cell_style.margin[TABLE_HORZ][0] = optional_px (s->margin_left, 8);
209       if (s->margin_right != DBL_MAX)
210         a->cell_style.margin[TABLE_HORZ][1] = optional_px (s->margin_right,
211                                                            11);
212       if (s->margin_top != DBL_MAX)
213         a->cell_style.margin[TABLE_VERT][0] = optional_px (s->margin_top, 1);
214       if (s->margin_bottom != DBL_MAX)
215         a->cell_style.margin[TABLE_VERT][1] = optional_px (s->margin_bottom,
216                                                            1);
217     }
218
219   for (int i = 0; i < PIVOT_N_BORDERS; i++)
220     pivot_border_get_default_style (i, &out->borders[i]);
221
222   const struct spvsx_border_properties *bp = in->border_properties;
223   for (size_t i = 0; i < bp->n_border_style; i++)
224     {
225       const struct spvsx_border_style *bin = bp->border_style[i];
226       const char *name = CHAR_CAST (const char *, bin->node_.raw->name);
227       enum pivot_border border = pivot_border_from_name (name);
228       if (border == PIVOT_N_BORDERS)
229         {
230           error = xasprintf ("unknown border \"%s\" parsing borderProperties",
231                              name);
232           goto error;
233         }
234
235       struct table_border_style *bout = &out->borders[border];
236       bout->stroke
237         = (bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_NONE
238            ? TABLE_STROKE_NONE
239            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_DASHED
240            ? TABLE_STROKE_DASHED
241            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_THICK
242            ? TABLE_STROKE_THICK
243            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_THIN
244            ? TABLE_STROKE_THIN
245            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_DOUBLE
246            ? TABLE_STROKE_DOUBLE
247            : TABLE_STROKE_SOLID);
248       bout->color = optional_color (bin->color,
249                                     (struct cell_color) CELL_COLOR_BLACK);
250     }
251
252   const struct spvsx_printing_properties *pp = in->printing_properties;
253   out->print_all_layers = pp->print_all_layers > 0;
254   out->paginate_layers = pp->print_each_layer_on_separate_page > 0;
255   out->shrink_to_width = pp->rescale_wide_table_to_fit_page > 0;
256   out->shrink_to_length = pp->rescale_long_table_to_fit_page > 0;
257   out->top_continuation = pp->continuation_text_at_top > 0;
258   out->bottom_continuation = pp->continuation_text_at_bottom > 0;
259   out->continuation = xstrdup (pp->continuation_text
260                                ? pp->continuation_text : "(cont.)");
261   out->n_orphan_lines = optional_int (pp->window_orphan_lines, 2);
262
263   *outp = out;
264   return NULL;
265
266 error:
267   spv_table_look_destroy (out);
268   *outp = NULL;
269   return error;
270 }
271
272 char * WARN_UNUSED_RESULT
273 spv_table_look_read (const char *filename, struct spv_table_look **outp)
274 {
275   *outp = NULL;
276
277   size_t length;
278   char *file = read_file (filename, 0, &length);
279   if (!file)
280     return xasprintf ("%s: failed to read file (%s)",
281                       filename, strerror (errno));
282
283   xmlDoc *doc = xmlReadMemory (file, length, NULL, NULL, XML_PARSE_NOBLANKS);
284   free (file);
285   if (!doc)
286     return xasprintf ("%s: failed to parse XML", filename);
287
288   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
289   struct spvsx_table_properties *tp;
290   spvsx_parse_table_properties (&ctx, xmlDocGetRootElement (doc), &tp);
291   char *error = spvxml_context_finish (&ctx, &tp->node_);
292
293   if (!error)
294     error = spv_table_look_decode (tp, outp);
295
296   spvsx_free_table_properties (tp);
297   xmlFreeDoc (doc);
298
299   return error;
300 }
301
302 void
303 spv_table_look_destroy (struct spv_table_look *look)
304 {
305   if (look)
306     {
307       for (size_t i = 0; i < PIVOT_N_AREAS; i++)
308         area_style_uninit (&look->areas[i]);
309       free (look->continuation);
310       free (look);
311     }
312 }
313
314 void
315 spv_table_look_install (const struct spv_table_look *look,
316                         struct pivot_table *table)
317 {
318   table->omit_empty = look->omit_empty;
319
320   for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
321     for (int i = 0; i < 2; i++)
322       if (look->width_ranges[axis][i] > 0)
323         table->sizing[axis].range[i] = look->width_ranges[axis][i];
324   table->row_labels_in_corner = look->row_labels_in_corner;
325
326   table->footnote_marker_superscripts = look->footnote_marker_superscripts;
327   table->show_numeric_markers = look->show_numeric_markers;
328
329   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
330     {
331       area_style_uninit (&table->areas[i]);
332       area_style_copy (NULL, &table->areas[i], &look->areas[i]);
333     }
334   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
335     table->borders[i] = look->borders[i];
336
337   table->print_all_layers = look->print_all_layers;
338   table->paginate_layers = look->paginate_layers;
339   table->shrink_to_fit[TABLE_HORZ] = look->shrink_to_width;
340   table->shrink_to_fit[TABLE_VERT] = look->shrink_to_length;
341   table->top_continuation = look->top_continuation;
342   table->bottom_continuation = look->bottom_continuation;
343   table->continuation = xstrdup (look->continuation);
344   table->n_orphan_lines = look->n_orphan_lines;
345 }