1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2017, 2018, 2020 Free Software Foundation, Inc.
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.
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.
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/>. */
19 #include "output/spv/spv-table-look.h"
23 #include <libxml/xmlreader.h>
24 #include <libxml/xmlwriter.h>
27 #include "output/spv/structure-xml-parser.h"
29 #include "gl/read-file.h"
30 #include "gl/xalloc.h"
33 #define _(msgid) gettext (msgid)
35 static struct cell_color
36 optional_color (int color, struct cell_color default_color)
39 ? (struct cell_color) CELL_COLOR (color >> 16, color >> 8, color)
44 optional_length (const char *s, int default_length)
46 /* There is usually a "pt" suffix. We ignore it. */
48 return s && sscanf (s, "%d", &length) == 1 ? length : default_length;
52 optional_px (double inches, int default_px)
54 return inches != DBL_MAX ? inches * 96.0 : default_px;
58 optional_int (int x, int default_value)
60 return x != INT_MIN ? x : default_value;
64 optional_pt (double inches, int default_pt)
66 return inches != DBL_MAX ? inches * 72.0 + .5 : default_pt;
69 static const char *pivot_area_names[PIVOT_N_AREAS] = {
70 [PIVOT_AREA_TITLE] = "title",
71 [PIVOT_AREA_CAPTION] = "caption",
72 [PIVOT_AREA_FOOTER] = "footnotes",
73 [PIVOT_AREA_CORNER] = "cornerLabels",
74 [PIVOT_AREA_COLUMN_LABELS] = "columnLabels",
75 [PIVOT_AREA_ROW_LABELS] = "rowLabels",
76 [PIVOT_AREA_DATA] = "data",
77 [PIVOT_AREA_LAYERS] = "layers",
80 static enum pivot_area
81 pivot_area_from_name (const char *name)
84 for (area = 0; area < PIVOT_N_AREAS; area++)
85 if (!strcmp (name, pivot_area_names[area]))
90 static const char *pivot_border_names[PIVOT_N_BORDERS] = {
91 [PIVOT_BORDER_TITLE] = "titleLayerSeparator",
92 [PIVOT_BORDER_OUTER_LEFT] = "leftOuterFrame",
93 [PIVOT_BORDER_OUTER_TOP] = "topOuterFrame",
94 [PIVOT_BORDER_OUTER_RIGHT] = "rightOuterFrame",
95 [PIVOT_BORDER_OUTER_BOTTOM] = "bottomOuterFrame",
96 [PIVOT_BORDER_INNER_LEFT] = "leftInnerFrame",
97 [PIVOT_BORDER_INNER_TOP] = "topInnerFrame",
98 [PIVOT_BORDER_INNER_RIGHT] = "rightInnerFrame",
99 [PIVOT_BORDER_INNER_BOTTOM] = "bottomInnerFrame",
100 [PIVOT_BORDER_DATA_LEFT] = "dataAreaLeft",
101 [PIVOT_BORDER_DATA_TOP] = "dataAreaTop",
102 [PIVOT_BORDER_DIM_ROW_HORZ] = "horizontalDimensionBorderRows",
103 [PIVOT_BORDER_DIM_ROW_VERT] = "verticalDimensionBorderRows",
104 [PIVOT_BORDER_DIM_COL_HORZ] = "horizontalDimensionBorderColumns",
105 [PIVOT_BORDER_DIM_COL_VERT] = "verticalDimensionBorderColumns",
106 [PIVOT_BORDER_CAT_ROW_HORZ] = "horizontalCategoryBorderRows",
107 [PIVOT_BORDER_CAT_ROW_VERT] = "verticalCategoryBorderRows",
108 [PIVOT_BORDER_CAT_COL_HORZ] = "horizontalCategoryBorderColumns",
109 [PIVOT_BORDER_CAT_COL_VERT] = "verticalCategoryBorderColumns",
112 static enum pivot_border
113 pivot_border_from_name (const char *name)
115 enum pivot_border border;
116 for (border = 0; border < PIVOT_N_BORDERS; border++)
117 if (!strcmp (name, pivot_border_names[border]))
122 char * WARN_UNUSED_RESULT
123 spv_table_look_decode (const struct spvsx_table_properties *in,
124 struct spv_table_look **outp)
126 struct spv_table_look *out = xzalloc (sizeof *out);
129 out->name = in->name ? xstrdup (in->name) : NULL;
131 const struct spvsx_general_properties *g = in->general_properties;
132 out->omit_empty = g->hide_empty_rows != 0;
133 out->width_ranges[TABLE_HORZ][0] = optional_pt (g->minimum_column_width, -1);
134 out->width_ranges[TABLE_HORZ][1] = optional_pt (g->maximum_column_width, -1);
135 out->width_ranges[TABLE_VERT][0] = optional_pt (g->minimum_row_width, -1);
136 out->width_ranges[TABLE_VERT][1] = optional_pt (g->maximum_row_width, -1);
137 out->row_labels_in_corner
138 = g->row_dimension_labels != SPVSX_ROW_DIMENSION_LABELS_NESTED;
140 const struct spvsx_footnote_properties *f = in->footnote_properties;
141 out->footnote_marker_superscripts
142 = (f->marker_position != SPVSX_MARKER_POSITION_SUBSCRIPT);
143 out->show_numeric_markers
144 = (f->number_format == SPVSX_NUMBER_FORMAT_NUMERIC);
146 for (int i = 0; i < PIVOT_N_AREAS; i++)
147 table_area_style_copy (NULL, &out->areas[i],
148 pivot_area_get_default_style (i));
150 const struct spvsx_cell_format_properties *cfp = in->cell_format_properties;
151 for (size_t i = 0; i < cfp->n_cell_style; i++)
153 const struct spvsx_cell_style *c = cfp->cell_style[i];
154 const char *name = CHAR_CAST (const char *, c->node_.raw->name);
155 enum pivot_area area = pivot_area_from_name (name);
156 if (area == PIVOT_N_AREAS)
158 error = xasprintf ("unknown area \"%s\" in cellFormatProperties",
163 struct table_area_style *a = &out->areas[area];
164 const struct spvsx_style *s = c->style;
166 a->font_style.bold = s->font_weight == SPVSX_FONT_WEIGHT_BOLD;
168 a->font_style.italic = s->font_style == SPVSX_FONT_STYLE_ITALIC;
169 if (s->font_underline)
170 a->font_style.underline
171 = s->font_underline == SPVSX_FONT_UNDERLINE_UNDERLINE;
173 a->font_style.fg[0] = optional_color (
174 s->color, (struct cell_color) CELL_COLOR_BLACK);
175 if (c->alternating_text_color >= 0 || s->color >= 0)
176 a->font_style.fg[1] = optional_color (c->alternating_text_color,
177 a->font_style.fg[0]);
179 a->font_style.bg[0] = optional_color (
180 s->color2, (struct cell_color) CELL_COLOR_WHITE);
181 if (c->alternating_color >= 0 || s->color2 >= 0)
182 a->font_style.bg[1] = optional_color (c->alternating_color,
183 a->font_style.bg[0]);
186 free (a->font_style.typeface);
187 a->font_style.typeface = xstrdup (s->font_family);
191 a->font_style.size = optional_length (s->font_size, 0);
193 if (s->text_alignment)
195 = (s->text_alignment == SPVSX_TEXT_ALIGNMENT_LEFT
197 : s->text_alignment == SPVSX_TEXT_ALIGNMENT_RIGHT
199 : s->text_alignment == SPVSX_TEXT_ALIGNMENT_CENTER
200 ? TABLE_HALIGN_CENTER
201 : s->text_alignment == SPVSX_TEXT_ALIGNMENT_DECIMAL
202 ? TABLE_HALIGN_DECIMAL
203 : TABLE_HALIGN_MIXED);
204 if (s->label_location_vertical)
206 = (s->label_location_vertical == SPVSX_LABEL_LOCATION_VERTICAL_NEGATIVE
207 ? TABLE_VALIGN_BOTTOM
208 : s->label_location_vertical == SPVSX_LABEL_LOCATION_VERTICAL_POSITIVE
210 : TABLE_VALIGN_CENTER);
212 if (s->decimal_offset != DBL_MAX)
213 a->cell_style.decimal_offset = optional_px (s->decimal_offset, 0);
215 if (s->margin_left != DBL_MAX)
216 a->cell_style.margin[TABLE_HORZ][0] = optional_px (s->margin_left, 8);
217 if (s->margin_right != DBL_MAX)
218 a->cell_style.margin[TABLE_HORZ][1] = optional_px (s->margin_right,
220 if (s->margin_top != DBL_MAX)
221 a->cell_style.margin[TABLE_VERT][0] = optional_px (s->margin_top, 1);
222 if (s->margin_bottom != DBL_MAX)
223 a->cell_style.margin[TABLE_VERT][1] = optional_px (s->margin_bottom,
227 for (int i = 0; i < PIVOT_N_BORDERS; i++)
228 pivot_border_get_default_style (i, &out->borders[i]);
230 const struct spvsx_border_properties *bp = in->border_properties;
231 for (size_t i = 0; i < bp->n_border_style; i++)
233 const struct spvsx_border_style *bin = bp->border_style[i];
234 const char *name = CHAR_CAST (const char *, bin->node_.raw->name);
235 enum pivot_border border = pivot_border_from_name (name);
236 if (border == PIVOT_N_BORDERS)
238 error = xasprintf ("unknown border \"%s\" parsing borderProperties",
243 struct table_border_style *bout = &out->borders[border];
245 = (bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_NONE
247 : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_DASHED
248 ? TABLE_STROKE_DASHED
249 : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_THICK
251 : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_THIN
253 : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_DOUBLE
254 ? TABLE_STROKE_DOUBLE
255 : TABLE_STROKE_SOLID);
256 bout->color = optional_color (bin->color,
257 (struct cell_color) CELL_COLOR_BLACK);
260 const struct spvsx_printing_properties *pp = in->printing_properties;
261 out->print_all_layers = pp->print_all_layers > 0;
262 out->paginate_layers = pp->print_each_layer_on_separate_page > 0;
263 out->shrink_to_width = pp->rescale_wide_table_to_fit_page > 0;
264 out->shrink_to_length = pp->rescale_long_table_to_fit_page > 0;
265 out->top_continuation = pp->continuation_text_at_top > 0;
266 out->bottom_continuation = pp->continuation_text_at_bottom > 0;
267 out->continuation = xstrdup (pp->continuation_text
268 ? pp->continuation_text : "(cont.)");
269 out->n_orphan_lines = optional_int (pp->window_orphan_lines, 2);
275 spv_table_look_destroy (out);
280 char * WARN_UNUSED_RESULT
281 spv_table_look_read (const char *filename, struct spv_table_look **outp)
286 char *file = read_file (filename, 0, &length);
288 return xasprintf ("%s: failed to read file (%s)",
289 filename, strerror (errno));
291 xmlDoc *doc = xmlReadMemory (file, length, NULL, NULL, XML_PARSE_NOBLANKS);
294 return xasprintf ("%s: failed to parse XML", filename);
296 struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
297 struct spvsx_table_properties *tp;
298 spvsx_parse_table_properties (&ctx, xmlDocGetRootElement (doc), &tp);
299 char *error = spvxml_context_finish (&ctx, &tp->node_);
302 error = spv_table_look_decode (tp, outp);
304 spvsx_free_table_properties (tp);
311 write_attr (xmlTextWriter *xml, const char *name, const char *value)
313 xmlTextWriterWriteAttribute (xml,
314 CHAR_CAST (xmlChar *, name),
315 CHAR_CAST (xmlChar *, value));
318 static void PRINTF_FORMAT (3, 4)
319 write_attr_format (xmlTextWriter *xml, const char *name,
320 const char *format, ...)
323 va_start (args, format);
324 char *value = xvasprintf (format, args);
327 write_attr (xml, name, value);
332 write_attr_color (xmlTextWriter *xml, const char *name,
333 const struct cell_color *color)
335 write_attr_format (xml, name, "#%02"PRIx8"%02"PRIx8"%02"PRIx8,
336 color->r, color->g, color->b);
340 write_attr_dimension (xmlTextWriter *xml, const char *name, int px)
342 int pt = px / 96.0 * 72.0;
343 write_attr_format (xml, name, "%dpt", pt);
347 write_attr_bool (xmlTextWriter *xml, const char *name, bool b)
349 write_attr (xml, name, b ? "true" : "false");
353 start_elem (xmlTextWriter *xml, const char *name)
355 xmlTextWriterStartElement (xml, CHAR_CAST (xmlChar *, name));
359 end_elem (xmlTextWriter *xml)
361 xmlTextWriterEndElement (xml);
364 char * WARN_UNUSED_RESULT
365 spv_table_look_write (const char *filename, const struct spv_table_look *look)
367 FILE *file = fopen (filename, "w");
369 return xasprintf (_("%s: create failed (%s)"), filename, strerror (errno));
371 xmlTextWriter *xml = xmlNewTextWriter (xmlOutputBufferCreateFile (
376 return xasprintf (_("%s: failed to start writing XML"), filename);
379 xmlTextWriterSetIndent (xml, 1);
380 xmlTextWriterSetIndentString (xml, CHAR_CAST (xmlChar *, " "));
382 xmlTextWriterStartDocument (xml, NULL, "UTF-8", NULL);
383 start_elem (xml, "tableProperties");
385 write_attr (xml, "name", look->name);
386 write_attr (xml, "xmlns", "http://www.ibm.com/software/analytics/spss/xml/table-looks");
387 write_attr (xml, "xmlns:vizml", "http://www.ibm.com/software/analytics/spss/xml/visualization");
388 write_attr (xml, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
389 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");
391 start_elem (xml, "generalProperties");
392 write_attr_bool (xml, "hideEmptyRows", look->omit_empty);
393 const int (*wr)[2] = look->width_ranges;
394 write_attr_format (xml, "maximumColumnWidth", "%d", wr[TABLE_HORZ][1]);
395 write_attr_format (xml, "maximumRowWidth", "%d", wr[TABLE_VERT][1]);
396 write_attr_format (xml, "minimumColumnWidth", "%d", wr[TABLE_HORZ][0]);
397 write_attr_format (xml, "minimumRowWidth", "%d", wr[TABLE_VERT][0]);
398 write_attr (xml, "rowDimensionLabels",
399 look->row_labels_in_corner ? "inCorner" : "nested");
402 start_elem (xml, "footnoteProperties");
403 write_attr (xml, "markerPosition",
404 look->footnote_marker_superscripts ? "superscript" : "subscript");
405 write_attr (xml, "numberFormat",
406 look->show_numeric_markers ? "numeric" : "alphabetic");
409 start_elem (xml, "cellFormatProperties");
410 for (enum pivot_area a = 0; a < PIVOT_N_AREAS; a++)
412 const struct table_area_style *area = &look->areas[a];
413 const struct font_style *font = &area->font_style;
414 const struct cell_style *cell = &area->cell_style;
416 start_elem (xml, pivot_area_names[a]);
417 if (a == PIVOT_AREA_DATA
418 && (!cell_color_equal (&font->fg[0], &font->fg[1])
419 || !cell_color_equal (&font->bg[0], &font->bg[1])))
421 write_attr_color (xml, "alternatingColor", &font->bg[1]);
422 write_attr_color (xml, "alternatingTextColor", &font->fg[1]);
425 start_elem (xml, "vizml:style");
426 write_attr_color (xml, "color", &font->fg[0]);
427 write_attr_color (xml, "color2", &font->bg[0]);
428 write_attr (xml, "font-family", font->typeface);
429 write_attr_format (xml, "font-size", "%dpt", font->size);
430 write_attr (xml, "font-weight", font->bold ? "bold" : "regular");
431 write_attr (xml, "font-underline",
432 font->underline ? "underline" : "none");
433 write_attr (xml, "labelLocationVertical",
434 cell->valign == TABLE_VALIGN_BOTTOM ? "negative"
435 : cell->valign == TABLE_VALIGN_TOP ? "positive"
437 write_attr_dimension (xml, "margin-bottom", cell->margin[TABLE_VERT][1]);
438 write_attr_dimension (xml, "margin-left", cell->margin[TABLE_HORZ][0]);
439 write_attr_dimension (xml, "margin-right", cell->margin[TABLE_HORZ][1]);
440 write_attr_dimension (xml, "margin-top", cell->margin[TABLE_VERT][0]);
441 write_attr (xml, "textAlignment",
442 cell->halign == TABLE_HALIGN_LEFT ? "left"
443 : cell->halign == TABLE_HALIGN_RIGHT ? "right"
444 : cell->halign == TABLE_HALIGN_CENTER ? "center"
445 : cell->halign == TABLE_HALIGN_DECIMAL ? "decimal"
447 if (cell->halign == TABLE_HALIGN_DECIMAL)
448 write_attr_dimension (xml, "decimal-offset", cell->decimal_offset);
455 start_elem (xml, "borderProperties");
456 for (enum pivot_border b = 0; b < PIVOT_N_BORDERS; b++)
458 const struct table_border_style *border = &look->borders[b];
460 start_elem (xml, pivot_border_names[b]);
462 static const char *table_stroke_names[TABLE_N_STROKES] =
464 [TABLE_STROKE_NONE] = "none",
465 [TABLE_STROKE_SOLID] = "solid",
466 [TABLE_STROKE_DASHED] = "dashed",
467 [TABLE_STROKE_THICK] = "thick",
468 [TABLE_STROKE_THIN] = "thin",
469 [TABLE_STROKE_DOUBLE] = "double",
471 write_attr (xml, "borderStyleType", table_stroke_names[border->stroke]);
472 write_attr_color (xml, "color", &border->color);
477 start_elem (xml, "printingProperties");
478 write_attr_bool (xml, "printAllLayers", look->print_all_layers);
479 write_attr_bool (xml, "rescaleLongTableToFitPage", look->shrink_to_length);
480 write_attr_bool (xml, "rescaleWideTableToFitPage", look->shrink_to_width);
481 write_attr_format (xml, "windowOrphanLines", "%zu", look->n_orphan_lines);
482 if (look->continuation && look->continuation[0]
483 && (look->top_continuation || look->bottom_continuation))
485 write_attr_format (xml, "continuationText", look->continuation);
486 write_attr_bool (xml, "continuationTextAtTop", look->top_continuation);
487 write_attr_bool (xml, "continuationTextAtBottom",
488 look->bottom_continuation);
492 xmlTextWriterEndDocument (xml);
494 xmlFreeTextWriter (xml);
497 bool ok = !ferror (file);
498 if (fclose (file) == EOF)
502 return xasprintf (_("%s: error writing file (%s)"),
503 filename, strerror (errno));
509 spv_table_look_destroy (struct spv_table_look *look)
514 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
515 table_area_style_uninit (&look->areas[i]);
516 free (look->continuation);
522 spv_table_look_install (const struct spv_table_look *look,
523 struct pivot_table *table)
525 free (table->table_look);
527 table->table_look = xstrdup (look->name);
529 table->omit_empty = look->omit_empty;
531 for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
532 for (int i = 0; i < 2; i++)
533 if (look->width_ranges[axis][i] > 0)
534 table->sizing[axis].range[i] = look->width_ranges[axis][i];
535 table->row_labels_in_corner = look->row_labels_in_corner;
537 table->footnote_marker_superscripts = look->footnote_marker_superscripts;
538 table->show_numeric_markers = look->show_numeric_markers;
540 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
542 table_area_style_uninit (&table->areas[i]);
543 table_area_style_copy (NULL, &table->areas[i], &look->areas[i]);
545 for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
546 table->borders[i] = look->borders[i];
548 table->print_all_layers = look->print_all_layers;
549 table->paginate_layers = look->paginate_layers;
550 table->shrink_to_fit[TABLE_HORZ] = look->shrink_to_width;
551 table->shrink_to_fit[TABLE_VERT] = look->shrink_to_length;
552 table->top_continuation = look->top_continuation;
553 table->bottom_continuation = look->bottom_continuation;
554 table->continuation = xstrdup (look->continuation);
555 table->n_orphan_lines = look->n_orphan_lines;
558 struct spv_table_look *
559 spv_table_look_get (const struct pivot_table *table)
561 struct spv_table_look *look = xzalloc (sizeof *look);
563 look->name = table->table_look ? xstrdup (table->table_look) : NULL;
565 look->omit_empty = table->omit_empty;
567 for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
568 for (int i = 0; i < 2; i++)
569 look->width_ranges[axis][i] = table->sizing[axis].range[i];
570 look->row_labels_in_corner = table->row_labels_in_corner;
572 look->footnote_marker_superscripts = table->footnote_marker_superscripts;
573 look->show_numeric_markers = table->show_numeric_markers;
575 for (size_t i = 0; i < PIVOT_N_AREAS; i++)
576 table_area_style_copy (NULL, &look->areas[i], &table->areas[i]);
577 for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
578 look->borders[i] = table->borders[i];
580 look->print_all_layers = table->print_all_layers;
581 look->paginate_layers = table->paginate_layers;
582 look->shrink_to_width = table->shrink_to_fit[TABLE_HORZ];
583 look->shrink_to_length = table->shrink_to_fit[TABLE_VERT];
584 look->top_continuation = table->top_continuation;
585 look->bottom_continuation = table->bottom_continuation;
586 look->continuation = xstrdup (table->continuation);
587 look->n_orphan_lines = table->n_orphan_lines;