+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;
+}
+