1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2019 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-writer.h"
20 
21 #include <inttypes.h>
22 #include <libxml/xmlwriter.h>
23 #include <math.h>
24 #include <stdlib.h>
25 #include <time.h>
26 
27 #include "libpspp/array.h"
28 #include "libpspp/assertion.h"
29 #include "libpspp/cast.h"
30 #include "libpspp/float-format.h"
31 #include "libpspp/integer-format.h"
32 #include "libpspp/temp-file.h"
33 #include "libpspp/version.h"
34 #include "libpspp/zip-writer.h"
35 #include "output/page-setup-item.h"
36 #include "output/pivot-table.h"
37 #include "output/text-item.h"
38 
39 #include "gl/xalloc.h"
40 #include "gl/xvasprintf.h"
41 
42 #include "gettext.h"
43 #define _(msgid) gettext (msgid)
44 #define N_(msgid) (msgid)
45 
46 struct spv_writer
47   {
48     struct zip_writer *zw;
49 
50     FILE *heading;
51     int heading_depth;
52     xmlTextWriter *xml;
53 
54     int n_tables;
55 
56     int n_headings;
57     struct page_setup *page_setup;
58     bool need_page_break;
59   };
60 
61 char * WARN_UNUSED_RESULT
62 spv_writer_open (const char *filename, struct spv_writer **writerp)
63 {
64   *writerp = NULL;
65 
66   struct zip_writer *zw = zip_writer_create (filename);
67   if (!zw)
68     return xasprintf (_("%s: create failed"), filename);
69 
70   struct spv_writer *w = xmalloc (sizeof *w);
71   *w = (struct spv_writer) { .zw = zw };
72   *writerp = w;
73   return NULL;
74 }
75 
76 char * WARN_UNUSED_RESULT
77 spv_writer_close (struct spv_writer *w)
78 {
79   if (!w)
80     return NULL;
81 
82   zip_writer_add_string (w->zw, "META-INF/MANIFEST.MF", "allowPivoting=true");
83 
84   while (w->heading_depth)
85     spv_writer_close_heading (w);
86 
87   char *error = NULL;
88   if (!zip_writer_close (w->zw))
89     error = xstrdup (_("I/O error writing SPV file"));
90 
91   page_setup_destroy (w->page_setup);
92   free (w);
93   return error;
94 }
95 
96 void
97 spv_writer_set_page_setup (struct spv_writer *w,
98                            const struct page_setup *page_setup)
99 {
100   page_setup_destroy (w->page_setup);
101   w->page_setup = page_setup_clone (page_setup);
102 }
103 
104 static void
105 write_attr (struct spv_writer *w, const char *name, const char *value)
106 {
107   xmlTextWriterWriteAttribute (w->xml,
108                                CHAR_CAST (xmlChar *, name),
109                                CHAR_CAST (xmlChar *, value));
110 }
111 
112 static void PRINTF_FORMAT (3, 4)
113 write_attr_format (struct spv_writer *w, const char *name,
114                    const char *format, ...)
115 {
116   va_list args;
117   va_start (args, format);
118   char *value = xvasprintf (format, args);
119   va_end (args);
120 
121   write_attr (w, name, value);
122   free (value);
123 }
124 
125 static void
126 start_elem (struct spv_writer *w, const char *name)
127 {
128   xmlTextWriterStartElement (w->xml, CHAR_CAST (xmlChar *, name));
129 }
130 
131 static void
132 end_elem (struct spv_writer *w)
133 {
134   xmlTextWriterEndElement (w->xml);
135 }
136 
137 static void
138 write_text (struct spv_writer *w, const char *text)
139 {
140   xmlTextWriterWriteString (w->xml, CHAR_CAST (xmlChar *, text));
141 }
142 
143 static void
144 write_page_heading (struct spv_writer *w, const struct page_heading *h,
145                     const char *name)
146 {
147   start_elem (w, name);
148   if (h->n)
149     {
150       start_elem (w, "pageParagraph");
151       for (size_t i = 0; i < h->n; i++)
152         {
153           start_elem (w, "text");
154           write_attr (w, "type", "title");
155           write_text (w, h->paragraphs[i].markup); /* XXX */
156           end_elem (w);
157         }
158       end_elem (w);
159     }
160   end_elem (w);
161 }
162 
163 static void
164 write_page_setup (struct spv_writer *w, const struct page_setup *ps)
165 {
166   start_elem (w, "pageSetup");
167   write_attr_format (w, "initial-page-number", "%d", ps->initial_page_number);
168   write_attr (w, "chart-size",
169               (ps->chart_size == PAGE_CHART_AS_IS ? "as-is"
170                : ps->chart_size == PAGE_CHART_FULL_HEIGHT ? "full-height"
171                : ps->chart_size == PAGE_CHART_HALF_HEIGHT ? "half-height"
172                : "quarter-height"));
173   write_attr_format (w, "margin-left", "%.2fin", ps->margins[TABLE_HORZ][0]);
174   write_attr_format (w, "margin-right", "%.2fin", ps->margins[TABLE_HORZ][1]);
175   write_attr_format (w, "margin-top", "%.2fin", ps->margins[TABLE_VERT][0]);
176   write_attr_format (w, "margin-bottom", "%.2fin", ps->margins[TABLE_VERT][1]);
177   write_attr_format (w, "paper-height", "%.2fin", ps->paper[TABLE_VERT]);
178   write_attr_format (w, "paper-width", "%.2fin", ps->paper[TABLE_HORZ]);
179   write_attr (w, "reference-orientation",
180               ps->orientation == PAGE_PORTRAIT ? "portrait" : "landscape");
181   write_attr_format (w, "space-after", "%.1fpt", ps->object_spacing * 72.0);
182   write_page_heading (w, &ps->headings[0], "pageHeader");
183   write_page_heading (w, &ps->headings[1], "pageFooter");
184   end_elem (w);
185 }
186 
187 static bool
188 spv_writer_open_file (struct spv_writer *w)
189 {
190   w->heading = create_temp_file ();
191   if (!w->heading)
192     return false;
193 
194   w->xml = xmlNewTextWriter (xmlOutputBufferCreateFile (w->heading, NULL));
195   xmlTextWriterStartDocument (w->xml, NULL, "UTF-8", NULL);
196   start_elem (w, "heading");
197 
198   time_t t = time (NULL);
199   struct tm *tm = gmtime (&t);
200   char *tm_s = asctime (tm);
201   write_attr (w, "creation-date-time", tm_s);
202 
203   write_attr (w, "creator", version);
204 
205   write_attr (w, "creator-version", "21");
206 
207   write_attr (w, "xmlns", "http://xml.spss.com/spss/viewer/viewer-tree");
208   write_attr (w, "xmlns:vps", "http://xml.spss.com/spss/viewer/viewer-pagesetup");
209   write_attr (w, "xmlns:vtx", "http://xml.spss.com/spss/viewer/viewer-text");
210   write_attr (w, "xmlns:vtb", "http://xml.spss.com/spss/viewer/viewer-table");
211 
212   start_elem (w, "label");
213   write_text (w, _("Output"));
214   end_elem (w);
215 
216   if (w->page_setup)
217     {
218       write_page_setup (w, w->page_setup);
219 
220       page_setup_destroy (w->page_setup);
221       w->page_setup = NULL;
222     }
223 
224   return true;
225 }
226 
227 void
228 spv_writer_open_heading (struct spv_writer *w, const char *command_id,
229                          const char *label)
230 {
231   if (!w->heading)
232     {
233       if (!spv_writer_open_file (w))
234         return;
235     }
236 
237   w->heading_depth++;
238   start_elem (w, "heading");
239   write_attr (w, "commandName", command_id);
240   /* XXX locale */
241   /* XXX olang */
242 
243   start_elem (w, "label");
244   write_text (w, label);
245   end_elem (w);
246 }
247 
248 static void
249 spv_writer_close_file (struct spv_writer *w, const char *infix)
250 {
251   if (!w->heading)
252     return;
253 
254   end_elem (w);
255   xmlTextWriterEndDocument (w->xml);
256   xmlFreeTextWriter (w->xml);
257 
258   char *member_name = xasprintf ("outputViewer%010d%s.xml",
259                                  w->n_headings++, infix);
260   zip_writer_add (w->zw, w->heading, member_name);
261   free (member_name);
262 
263   w->heading = NULL;
264 }
265 
266 void
267 spv_writer_close_heading (struct spv_writer *w)
268 {
269   const char *infix = "";
270   if (w->heading_depth)
271     {
272       infix = "_heading";
273       end_elem (w);
274       w->heading_depth--;
275     }
276 
277   if (!w->heading_depth)
278     spv_writer_close_file (w, infix);
279 }
280 
281 static void
282 start_container (struct spv_writer *w)
283 {
284   start_elem (w, "container");
285   write_attr (w, "visibility", "visible");
286   if (w->need_page_break)
287     {
288       write_attr (w, "page-break-before", "always");
289       w->need_page_break = false;
290     }
291 }
292 
293 void
294 spv_writer_put_text (struct spv_writer *w, const struct text_item *text,
295                      const char *command_id)
296 {
297   if (text->type == TEXT_ITEM_EJECT_PAGE)
298     w->need_page_break = true;
299 
300   bool initial_depth = w->heading_depth;
301   if (!initial_depth)
302     spv_writer_open_file (w);
303 
304   start_container (w);
305 
306   start_elem (w, "label");
307   write_text (w, (text->type == TEXT_ITEM_TITLE ? "Title"
308                   : text->type == TEXT_ITEM_PAGE_TITLE ? "Page Title"
309                   : "Log"));
310   end_elem (w);
311 
312   start_elem (w, "vtx:text");
313   write_attr (w, "type", (text->type == TEXT_ITEM_TITLE ? "title"
314                           : text->type == TEXT_ITEM_PAGE_TITLE ? "page-title"
315                           : "log"));
316   if (command_id)
317     write_attr (w, "commandName", command_id);
318 
319   start_elem (w, "html");
320   write_text (w, text->text);   /* XXX */
321   end_elem (w); /* html */
322   end_elem (w); /* vtx:text */
323   end_elem (w); /* container */
324 
325   if (!initial_depth)
326     spv_writer_close_file (w, "");
327 }
328 
329 #define H TABLE_HORZ
330 #define V TABLE_VERT
331 
332 struct buf
333   {
334     uint8_t *data;
335     size_t len;
336     size_t allocated;
337   };
338 
339 static uint8_t *
340 put_uninit (struct buf *b, size_t n)
341 {
342   while (b->allocated - b->len < n)
343     b->data = x2nrealloc (b->data, &b->allocated, sizeof b->data);
344   uint8_t *p = &b->data[b->len];
345   b->len += n;
346   return p;
347 }
348 
349 static void
350 put_byte (struct buf *b, uint8_t byte)
351 {
352   *put_uninit (b, 1) = byte;
353 }
354 
355 static void
356 put_bool (struct buf *b, bool boolean)
357 {
358   put_byte (b, boolean);
359 }
360 
361 static void
362 put_bytes (struct buf *b, const char *bytes, size_t n)
363 {
364   memcpy (put_uninit (b, n), bytes, n);
365 }
366 
367 static void
368 put_u16 (struct buf *b, uint16_t x)
369 {
370   put_uint16 (native_to_le16 (x), put_uninit (b, sizeof x));
371 }
372 
373 static void
374 put_u32 (struct buf *b, uint32_t x)
375 {
376   put_uint32 (native_to_le32 (x), put_uninit (b, sizeof x));
377 }
378 
379 static void
380 put_u64 (struct buf *b, uint64_t x)
381 {
382   put_uint64 (native_to_le64 (x), put_uninit (b, sizeof x));
383 }
384 
385 static void
386 put_be32 (struct buf *b, uint32_t x)
387 {
388   put_uint32 (native_to_be32 (x), put_uninit (b, sizeof x));
389 }
390 
391 static void
392 put_double (struct buf *b, double x)
393 {
394   float_convert (FLOAT_NATIVE_DOUBLE, &x,
395                  FLOAT_IEEE_DOUBLE_LE, put_uninit (b, 8));
396 }
397 
398 static void
399 put_float (struct buf *b, float x)
400 {
401   float_convert (FLOAT_NATIVE_FLOAT, &x,
402                  FLOAT_IEEE_SINGLE_LE, put_uninit (b, 4));
403 }
404 
405 static void
406 put_string (struct buf *b, const char *s_)
407 {
408   const char *s = s_ ? s_ : "";
409   size_t len = strlen (s);
410   put_u32 (b, len);
411   memcpy (put_uninit (b, len), s, len);
412 }
413 
414 static void
415 put_bestring (struct buf *b, const char *s_)
416 {
417   const char *s = s_ ? s_ : "";
418   size_t len = strlen (s);
419   put_be32 (b, len);
420   memcpy (put_uninit (b, len), s, len);
421 }
422 
423 static size_t
424 start_count (struct buf *b)
425 {
426   put_u32 (b, 0);
427   return b->len;
428 }
429 
430 static void
431 end_count_u32 (struct buf *b, size_t start)
432 {
433   put_uint32 (native_to_le32 (b->len - start), &b->data[start - 4]);
434 }
435 
436 static void
437 end_count_be32 (struct buf *b, size_t start)
438 {
439   put_uint32 (native_to_be32 (b->len - start), &b->data[start - 4]);
440 }
441 
442 static void
443 put_color (struct buf *buf, const struct cell_color *color)
444 {
445   char *s = xasprintf ("#%02"PRIx8"%02"PRIx8"%02"PRIx8,
446                        color->r, color->g, color->b);
447   put_string (buf, s);
448   free (s);
449 }
450 
451 static void
452 put_font_style (struct buf *buf, const struct font_style *font_style)
453 {
454   put_bool (buf, font_style->bold);
455   put_bool (buf, font_style->italic);
456   put_bool (buf, font_style->underline);
457   put_bool (buf, 1);
458   put_color (buf, &font_style->fg[0]);
459   put_color (buf, &font_style->bg[0]);
460   put_string (buf, font_style->typeface ? font_style->typeface : "SansSerif");
461   put_byte (buf, ceil (font_style->size * 1.33));
462 }
463 
464 static void
465 put_halign (struct buf *buf, enum table_halign halign,
466             uint32_t mixed, uint32_t decimal)
467 {
468   put_u32 (buf, (halign == TABLE_HALIGN_RIGHT ? 4
469                  : halign == TABLE_HALIGN_LEFT ? 2
470                  : halign == TABLE_HALIGN_CENTER ? 0
471                  : halign == TABLE_HALIGN_MIXED ? mixed
472                  : decimal));
473 }
474 
475 static void
476 put_valign (struct buf *buf, enum table_valign valign)
477 {
478   put_u32 (buf, (valign == TABLE_VALIGN_TOP ? 1
479                  : valign == TABLE_VALIGN_CENTER ? 0
480                  : 3));
481 }
482 
483 static void
484 put_cell_style (struct buf *buf, const struct cell_style *cell_style)
485 {
486   put_halign (buf, cell_style->halign, 0xffffffad, 6);
487   put_valign (buf, cell_style->valign);
488   put_double (buf, cell_style->decimal_offset);
489   put_u16 (buf, cell_style->margin[H][0]);
490   put_u16 (buf, cell_style->margin[H][1]);
491   put_u16 (buf, cell_style->margin[V][0]);
492   put_u16 (buf, cell_style->margin[V][1]);
493 }
494 
495 static void UNUSED
496 put_style_pair (struct buf *buf, const struct font_style *font_style,
497                 const struct cell_style *cell_style)
498 {
499   if (font_style)
500     {
501       put_byte (buf, 0x31);
502       put_font_style (buf, font_style);
503     }
504   else
505     put_byte (buf, 0x58);
506 
507   if (cell_style)
508     {
509       put_byte (buf, 0x31);
510       put_cell_style (buf, cell_style);
511     }
512   else
513     put_byte (buf, 0x58);
514 }
515 
516 static void
517 put_value_mod (struct buf *buf, const struct pivot_value *value,
518                const char *template)
519 {
520   if (value->n_footnotes || value->n_subscripts
521       || template || value->font_style || value->cell_style)
522     {
523       put_byte (buf, 0x31);
524 
525       /* Footnotes. */
526       put_u32 (buf, value->n_footnotes);
527       for (size_t i = 0; i < value->n_footnotes; i++)
528         put_u16 (buf, value->footnotes[i]->idx);
529 
530       /* Subscripts. */
531       put_u32 (buf, value->n_subscripts);
532       for (size_t i = 0; i < value->n_subscripts; i++)
533         put_string (buf, value->subscripts[i]);
534 
535       /* Template and style. */
536       uint32_t v3_start = start_count (buf);
537       uint32_t template_string_start = start_count (buf);
538       if (template)
539         {
540           uint32_t inner_start = start_count (buf);
541           end_count_u32 (buf, inner_start);
542 
543           put_byte (buf, 0x31);
544           put_string (buf, template);
545         }
546       end_count_u32 (buf, template_string_start);
547       put_style_pair (buf, value->font_style, value->cell_style);
548       end_count_u32 (buf, v3_start);
549     }
550   else
551     put_byte (buf, 0x58);
552 }
553 
554 static void
555 put_format (struct buf *buf, const struct fmt_spec *f)
556 {
557   put_u32 (buf, (fmt_to_io (f->type) << 16) | (f->w << 8) | f->d);
558 }
559 
560 static int
561 show_values_to_spvlb (enum settings_value_show show)
562 {
563   return (show == SETTINGS_VALUE_SHOW_DEFAULT ? 0
564           : show == SETTINGS_VALUE_SHOW_VALUE ? 1
565           : show == SETTINGS_VALUE_SHOW_LABEL ? 2
566           : 3);
567 }
568 
569 static void
570 put_show_values (struct buf *buf, enum settings_value_show show)
571 {
572   put_byte (buf, show_values_to_spvlb (show));
573 }
574 
575 static void
576 put_value (struct buf *buf, const struct pivot_value *value)
577 {
578   switch (value->type)
579     {
580     case PIVOT_VALUE_NUMERIC:
581       if (value->numeric.var_name || value->numeric.value_label)
582         {
583           put_byte (buf, 2);
584           put_value_mod (buf, value, NULL);
585           put_format (buf, &value->numeric.format);
586           put_double (buf, value->numeric.x);
587           put_string (buf, value->numeric.var_name);
588           put_string (buf, value->numeric.value_label);
589           put_show_values (buf, value->numeric.show);
590         }
591       else
592         {
593           put_byte (buf, 1);
594           put_value_mod (buf, value, NULL);
595           put_format (buf, &value->numeric.format);
596           put_double (buf, value->numeric.x);
597         }
598       break;
599 
600     case PIVOT_VALUE_STRING:
601       put_byte (buf, 4);
602       put_value_mod (buf, value, NULL);
603       put_format (buf,
604                   &(struct fmt_spec) { FMT_A, strlen (value->string.s), 0 });
605       put_string (buf, value->string.value_label);
606       put_string (buf, value->string.var_name);
607       put_show_values (buf, value->string.show);
608       put_string (buf, value->string.s);
609       break;
610 
611     case PIVOT_VALUE_VARIABLE:
612       put_byte (buf, 5);
613       put_value_mod (buf, value, NULL);
614       put_string (buf, value->variable.var_name);
615       put_string (buf, value->variable.var_label);
616       put_show_values (buf, value->variable.show);
617       break;
618 
619     case PIVOT_VALUE_TEXT:
620       put_byte (buf, 3);
621       put_string (buf, value->text.local);
622       put_value_mod (buf, value, NULL);
623       put_string (buf, value->text.id);
624       put_string (buf, value->text.c);
625       put_byte (buf, 1);        /* XXX user-provided */
626       break;
627 
628     case PIVOT_VALUE_TEMPLATE:
629       put_byte (buf, 0);
630       put_value_mod (buf, value, value->template.id);
631       put_string (buf, value->template.local);
632       put_u32 (buf, value->template.n_args);
633       for (size_t i = 0; i < value->template.n_args; i++)
634         {
635           const struct pivot_argument *arg = &value->template.args[i];
636           assert (arg->n >= 1);
637           if (arg->n > 1)
638             {
639               put_u32 (buf, arg->n);
640               put_u32 (buf, 0);
641               for (size_t j = 0; j < arg->n; j++)
642                 {
643                   if (j > 0)
644                     put_bytes (buf, "\0\0\0\0", 4);
645                   put_value (buf, arg->values[j]);
646                 }
647             }
648           else
649             {
650               put_u32 (buf, 0);
651               put_value (buf, arg->values[0]);
652             }
653         }
654       break;
655 
656     default:
657       NOT_REACHED ();
658     }
659 }
660 
661 static void
662 put_optional_value (struct buf *buf, const struct pivot_value *value)
663 {
664   if (value)
665     {
666       put_byte (buf, 0x31);
667       put_value (buf, value);
668     }
669   else
670     put_byte (buf, 0x58);
671 }
672 
673 static void
674 put_category (struct buf *buf, const struct pivot_category *c)
675 {
676   put_value (buf, c->name);
677   if (pivot_category_is_leaf (c))
678     {
679       put_bytes (buf, "\0\0\0", 3);
680       put_u32 (buf, 2);
681       put_u32 (buf, c->data_index);
682       put_u32 (buf, 0);
683     }
684   else
685     {
686       put_bytes (buf, "\0\0\1", 3);
687       put_u32 (buf, 0);
688       put_u32 (buf, -1);
689       put_u32 (buf, c->n_subs);
690       for (size_t i = 0; i < c->n_subs; i++)
691         put_category (buf, c->subs[i]);
692     }
693 }
694 
695 static void
696 put_y0 (struct buf *buf, const struct pivot_table *table)
697 {
698   put_u32 (buf, table->epoch);
699   put_byte (buf, table->decimal);
700   put_byte (buf, table->grouping);
701 }
702 
703 static void
704 put_custom_currency (struct buf *buf, const struct pivot_table *table)
705 {
706   put_u32 (buf, 5);
707   for (int i = 0; i < 5; i++)
708     put_string (buf, table->ccs[i]);
709 }
710 
711 static void
712 put_x1 (struct buf *buf, const struct pivot_table *table)
713 {
714   put_bytes (buf, "\0\1\0", 3);
715   put_byte (buf, 0);
716   put_show_values (buf, table->show_variables);
717   put_show_values (buf, table->show_values);
718   put_u32 (buf, -1);
719   put_u32 (buf, -1);
720   for (int i = 0; i < 17; i++)
721     put_byte (buf, 0);
722   put_bool (buf, false);
723   put_byte (buf, 1);
724 }
725 
726 static void
727 put_x2 (struct buf *buf)
728 {
729   put_u32 (buf, 0);             /* n-row-heights */
730   put_u32 (buf, 0);             /* n-style-map */
731   put_u32 (buf, 0);             /* n-styles */
732   put_u32 (buf, 0);
733 }
734 
735 static void
736 put_x3 (struct buf *buf, const struct pivot_table *table)
737 {
738   put_bytes (buf, "\1\0\4\0\0\0", 6);
739   put_string (buf, table->command_c);
740   put_string (buf, table->command_local);
741   put_string (buf, table->language);
742   put_string (buf, "UTF-8");    /* XXX */
743   put_string (buf, table->locale);
744   put_bytes (buf, "\0\0\1\1", 4);
745   put_y0 (buf, table);
746   put_double (buf, table->small);
747   put_byte (buf, 1);
748   put_string (buf, table->dataset);
749   put_string (buf, table->datafile);
750   put_u32 (buf, 0);
751   put_u32 (buf, table->date);
752   put_u32 (buf, 0);
753 
754   /* Y2. */
755   put_custom_currency (buf, table);
756   put_byte (buf, '.');
757   put_bool (buf, 0);
758 }
759 
760 static void
761 put_light_table (struct buf *buf, uint64_t table_id,
762                  const struct pivot_table *table)
763 {
764   /* Header. */
765   put_bytes (buf, "\1\0", 2);
766   put_u32 (buf, 3);
767   put_bool (buf, true);
768   put_bool (buf, false);
769   put_bool (buf, table->rotate_inner_column_labels);
770   put_bool (buf, table->rotate_outer_row_labels);
771   put_bool (buf, true);
772   put_u32 (buf, 0x15);
773   put_u32 (buf, table->sizing[H].range[0]);
774   put_u32 (buf, table->sizing[H].range[1]);
775   put_u32 (buf, table->sizing[V].range[0]);
776   put_u32 (buf, table->sizing[V].range[1]);
777   put_u64 (buf, table_id);
778 
779   /* Titles. */
780   put_value (buf, table->title);
781   put_value (buf, table->subtype);
782   put_optional_value (buf, table->title);
783   put_optional_value (buf, table->corner_text);
784   put_optional_value (buf, table->caption);
785 
786   /* Footnotes. */
787   put_u32 (buf, table->n_footnotes);
788   for (size_t i = 0; i < table->n_footnotes; i++)
789     {
790       put_value (buf, table->footnotes[i]->content);
791       put_optional_value (buf, table->footnotes[i]->marker);
792       put_u32 (buf, 0);
793     }
794 
795   /* Areas. */
796   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
797     {
798       const struct area_style *a = &table->areas[i];
799       put_byte (buf, i + 1);
800       put_byte (buf, 0x31);
801       put_string (buf, (a->font_style.typeface
802                         ? a->font_style.typeface
803                         : "SansSerif"));
804       put_float (buf, ceil (a->font_style.size * 1.33));
805       put_u32 (buf, ((a->font_style.bold ? 1 : 0)
806                      | (a->font_style.italic ? 2 : 0)));
807       put_bool (buf, a->font_style.underline);
808       put_halign (buf, a->cell_style.halign, 64173, 61453);
809       put_valign (buf, a->cell_style.valign);
810 
811       put_color (buf, &a->font_style.fg[0]);
812       put_color (buf, &a->font_style.bg[0]);
813 
814       bool alt
815         = (!cell_color_equal (&a->font_style.fg[0], &a->font_style.fg[1])
816            || !cell_color_equal (&a->font_style.bg[0], &a->font_style.bg[1]));
817       put_bool (buf, alt);
818       if (alt)
819         {
820           put_color (buf, &a->font_style.fg[1]);
821           put_color (buf, &a->font_style.bg[1]);
822         }
823       else
824         {
825           put_string (buf, "");
826           put_string (buf, "");
827         }
828 
829       put_u32 (buf, a->cell_style.margin[H][0]);
830       put_u32 (buf, a->cell_style.margin[H][1]);
831       put_u32 (buf, a->cell_style.margin[V][0]);
832       put_u32 (buf, a->cell_style.margin[V][1]);
833     }
834 
835   /* Borders. */
836   uint32_t borders_start = start_count (buf);
837   put_be32 (buf, 1);
838   put_be32 (buf, PIVOT_N_BORDERS);
839   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
840     {
841       const struct table_border_style *b = &table->borders[i];
842       put_be32 (buf, i);
843       put_be32 (buf, (b->stroke == TABLE_STROKE_NONE ? 0
844                       : b->stroke == TABLE_STROKE_SOLID ? 1
845                       : b->stroke == TABLE_STROKE_DASHED ? 2
846                       : b->stroke == TABLE_STROKE_THICK ? 3
847                       : b->stroke == TABLE_STROKE_THIN ? 4
848                       : 5));
849       put_be32 (buf, ((b->color.alpha << 24)
850                       | (b->color.r << 16)
851                       | (b->color.g << 8)
852                       | b->color.b));
853     }
854   put_bool (buf, table->show_grid_lines);
855   put_bytes (buf, "\0\0\0", 3);
856   end_count_u32 (buf, borders_start);
857 
858   /* Print Settings. */
859   uint32_t ps_start = start_count (buf);
860   put_be32 (buf, 1);
861   put_bool (buf, table->print_all_layers);
862   put_bool (buf, table->paginate_layers);
863   put_bool (buf, table->shrink_to_fit[H]);
864   put_bool (buf, table->shrink_to_fit[V]);
865   put_bool (buf, table->top_continuation);
866   put_bool (buf, table->bottom_continuation);
867   put_be32 (buf, table->n_orphan_lines);
868   put_bestring (buf, table->continuation);
869   end_count_u32 (buf, ps_start);
870 
871   /* Table Settings. */
872   uint32_t ts_start = start_count (buf);
873   put_be32 (buf, 1);
874   put_be32 (buf, 4);
875   put_be32 (buf, 0);            /* XXX current_layer */
876   put_bool (buf, table->omit_empty);
877   put_bool (buf, table->row_labels_in_corner);
878   put_bool (buf, !table->show_numeric_markers);
879   put_bool (buf, table->footnote_marker_superscripts);
880   put_byte (buf, 0);
881   uint32_t keep_start = start_count (buf);
882   put_be32 (buf, 0);            /* n-row-breaks */
883   put_be32 (buf, 0);            /* n-column-breaks */
884   put_be32 (buf, 0);            /* n-row-keeps */
885   put_be32 (buf, 0);            /* n-column-keeps */
886   put_be32 (buf, 0);            /* n-row-point-keeps */
887   put_be32 (buf, 0);            /* n-column-point-keeps */
888   end_count_be32 (buf, keep_start);
889   put_bestring (buf, table->notes);
890   put_bestring (buf, table->table_look);
891   for (size_t i = 0; i < 82; i++)
892     put_byte (buf, 0);
893   end_count_u32 (buf, ts_start);
894 
895   /* Formats. */
896   put_u32 (buf, 0);             /* n-widths */
897   put_string (buf, "en_US.ISO_8859-1:1987"); /* XXX */
898   put_u32 (buf, 0);                /* XXX current-layer */
899   put_bool (buf, 0);
900   put_bool (buf, 0);
901   put_bool (buf, 1);
902   put_y0 (buf, table);
903   put_custom_currency (buf, table);
904   uint32_t formats_start = start_count (buf);
905   uint32_t x1_start = start_count (buf);
906   put_x1 (buf, table);
907   uint32_t x2_start = start_count (buf);
908   put_x2 (buf);
909   end_count_u32 (buf, x2_start);
910   end_count_u32 (buf, x1_start);
911   uint32_t x3_start = start_count (buf);
912   put_x3 (buf, table);
913   end_count_u32 (buf, x3_start);
914   end_count_u32 (buf, formats_start);
915 
916   /* Dimensions. */
917   put_u32 (buf, table->n_dimensions);
918   int *x2 = xnmalloc (table->n_dimensions, sizeof *x2);
919   for (size_t i = 0; i < table->axes[PIVOT_AXIS_LAYER].n_dimensions; i++)
920     x2[i] = 2;
921   for (size_t i = 0; i < table->axes[PIVOT_AXIS_ROW].n_dimensions; i++)
922     x2[i + table->axes[PIVOT_AXIS_LAYER].n_dimensions] = 0;
923   for (size_t i = 0; i < table->axes[PIVOT_AXIS_COLUMN].n_dimensions; i++)
924     x2[i
925        + table->axes[PIVOT_AXIS_LAYER].n_dimensions
926        + table->axes[PIVOT_AXIS_ROW].n_dimensions] = 1;
927   for (size_t i = 0; i < table->n_dimensions; i++)
928     {
929       const struct pivot_dimension *d = table->dimensions[i];
930       put_value (buf, d->root->name);
931       put_byte (buf, 0);
932       put_byte (buf, x2[i]);
933       put_u32 (buf, 2);
934       put_bool (buf, !d->root->show_label);
935       put_bool (buf, d->hide_all_labels);
936       put_bool (buf, 1);
937       put_u32 (buf, i);
938 
939       put_u32 (buf, d->root->n_subs);
940       for (size_t j = 0; j < d->root->n_subs; j++)
941         put_category (buf, d->root->subs[j]);
942     }
943   free (x2);
944 
945   /* Axes. */
946   put_u32 (buf, table->axes[PIVOT_AXIS_LAYER].n_dimensions);
947   put_u32 (buf, table->axes[PIVOT_AXIS_ROW].n_dimensions);
948   put_u32 (buf, table->axes[PIVOT_AXIS_COLUMN].n_dimensions);
949   for (size_t i = 0; i < table->axes[PIVOT_AXIS_LAYER].n_dimensions; i++)
950     put_u32 (buf, table->axes[PIVOT_AXIS_LAYER].dimensions[i]->top_index);
951   for (size_t i = 0; i < table->axes[PIVOT_AXIS_ROW].n_dimensions; i++)
952     put_u32 (buf, table->axes[PIVOT_AXIS_ROW].dimensions[i]->top_index);
953   for (size_t i = 0; i < table->axes[PIVOT_AXIS_COLUMN].n_dimensions; i++)
954     put_u32 (buf, table->axes[PIVOT_AXIS_COLUMN].dimensions[i]->top_index);
955 
956   /* Cells. */
957   put_u32 (buf, hmap_count (&table->cells));
958   const struct pivot_cell *cell;
959   HMAP_FOR_EACH (cell, struct pivot_cell, hmap_node, &table->cells)
960     {
961       uint64_t index = 0;
962       for (size_t j = 0; j < table->n_dimensions; j++)
963         index = (table->dimensions[j]->n_leaves * index) + cell->idx[j];
964       put_u64 (buf, index);
965 
966       put_value (buf, cell->value);
967     }
968 }
969 
970 void
971 spv_writer_put_table (struct spv_writer *w, const struct pivot_table *table)
972 {
973   struct pivot_table *table_rw = CONST_CAST (struct pivot_table *, table);
974   if (!table_rw->subtype)
975     table_rw->subtype = pivot_value_new_user_text ("unknown", -1);
976 
977   int table_id = ++w->n_tables;
978 
979   bool initial_depth = w->heading_depth;
980   if (!initial_depth)
981     spv_writer_open_file (w);
982 
983   start_container (w);
984 
985   char *title = pivot_value_to_string (table->title,
986                                        SETTINGS_VALUE_SHOW_DEFAULT,
987                                        SETTINGS_VALUE_SHOW_DEFAULT);
988 
989   char *subtype = pivot_value_to_string (table->subtype,
990                                          SETTINGS_VALUE_SHOW_DEFAULT,
991                                          SETTINGS_VALUE_SHOW_DEFAULT);
992 
993   start_elem (w, "label");
994   write_text (w, title);
995   end_elem (w);
996 
997   start_elem (w, "vtb:table");
998   write_attr (w, "commandName", table->command_c);
999   write_attr (w, "type", "table"); /* XXX */
1000   write_attr (w, "subType", subtype);
1001   write_attr_format (w, "tableId", "%d", table_id);
1002 
1003   free (subtype);
1004   free (title);
1005 
1006   start_elem (w, "vtb:tableStructure");
1007   start_elem (w, "vtb:dataPath");
1008   char *data_path = xasprintf ("%010d_lightTableData.bin", table_id);
1009   write_text (w, data_path);
1010   end_elem (w); /* vtb:dataPath */
1011   end_elem (w); /* vtb:tableStructure */
1012   end_elem (w); /* vtb:table */
1013   end_elem (w); /* container */
1014 
1015   if (!initial_depth)
1016     spv_writer_close_file (w, "");
1017 
1018   struct buf buf = { NULL, 0, 0 };
1019   put_light_table (&buf, table_id, table);
1020   zip_writer_add_memory (w->zw, data_path, buf.data, buf.len);
1021   free (buf.data);
1022 
1023   free (data_path);
1024 }
1025