work
[pspp] / src / output / odt.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009-2014 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 /* A driver for creating OpenDocument Format text files from PSPP's output */
20
21 #include <errno.h>
22 #include <libgen.h>
23 #include <libxml/xmlwriter.h>
24 #ifdef HAVE_PWD_H
25 #include <pwd.h>
26 #endif
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <time.h>
30 #include <unistd.h>
31
32 #include "libpspp/assertion.h"
33 #include "libpspp/cast.h"
34 #include "libpspp/message.h"
35 #include "libpspp/str.h"
36 #include "libpspp/temp-file.h"
37 #include "libpspp/version.h"
38 #include "libpspp/zip-writer.h"
39 #include "data/file-handle-def.h"
40 #include "output/driver-provider.h"
41 #include "output/message-item.h"
42 #include "output/options.h"
43 #include "output/pivot-table.h"
44 #include "output/pivot-output.h"
45 #include "output/table-item.h"
46 #include "output/table-provider.h"
47 #include "output/text-item.h"
48
49 #include "gl/xalloc.h"
50
51 #include "gettext.h"
52 #define _(msgid) gettext (msgid)
53
54 #define _xml(X) (CHAR_CAST (const xmlChar *, X))
55
56 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
57 #define H TABLE_HORZ
58 #define V TABLE_VERT
59
60 struct odt_driver
61 {
62   struct output_driver driver;
63
64   struct zip_writer *zip;     /* ZIP file writer. */
65   struct file_handle *handle; /* Handle for 'file_name'. */
66   char *file_name;            /* Output file name. */
67
68   /* content.xml */
69   xmlTextWriterPtr content_wtr; /* XML writer. */
70   FILE *content_file;           /* Temporary file. */
71
72   /* manifest.xml */
73   xmlTextWriterPtr manifest_wtr; /* XML writer. */
74   FILE *manifest_file;           /* Temporary file. */
75
76   /* Number of tables so far. */
77   int table_num;
78 };
79
80 static const struct output_driver_class odt_driver_class;
81
82 static struct odt_driver *
83 odt_driver_cast (struct output_driver *driver)
84 {
85   assert (driver->class == &odt_driver_class);
86   return UP_CAST (driver, struct odt_driver, driver);
87 }
88
89 /* Creates a new temporary file and stores it in *FILE, then creates an XML
90    writer for it and stores it in *W. */
91 static void
92 create_writer (FILE **file, xmlTextWriterPtr *w)
93 {
94   /* XXX this can fail */
95   *file = create_temp_file ();
96   *w = xmlNewTextWriter (xmlOutputBufferCreateFile (*file, NULL));
97
98   xmlTextWriterStartDocument (*w, NULL, "UTF-8", NULL);
99 }
100
101
102 static void
103 register_file (struct odt_driver *odt, const char *filename)
104 {
105   assert (odt->manifest_wtr);
106   xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:file-entry"));
107   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:media-type"),  _xml("text/xml"));
108   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:full-path"),  _xml (filename));
109   xmlTextWriterEndElement (odt->manifest_wtr);
110 }
111
112 static void
113 write_style_data (struct odt_driver *odt)
114 {
115   xmlTextWriterPtr w;
116   FILE *file;
117
118   create_writer (&file, &w);
119   register_file (odt, "styles.xml");
120
121   xmlTextWriterStartElement (w, _xml ("office:document-styles"));
122   xmlTextWriterWriteAttribute (w, _xml ("xmlns:office"),
123                                _xml ("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
124
125   xmlTextWriterWriteAttribute (w, _xml ("xmlns:style"),
126                                _xml ("urn:oasis:names:tc:opendocument:xmlns:style:1.0"));
127
128   xmlTextWriterWriteAttribute (w, _xml ("xmlns:fo"),
129                                _xml ("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"));
130
131   xmlTextWriterWriteAttribute (w, _xml ("office:version"),  _xml ("1.1"));
132
133
134
135   xmlTextWriterStartElement (w, _xml ("office:styles"));
136
137
138   {
139     xmlTextWriterStartElement (w, _xml ("style:style"));
140     xmlTextWriterWriteAttribute (w, _xml ("style:name"),
141                                  _xml ("Standard"));
142
143     xmlTextWriterWriteAttribute (w, _xml ("style:family"),
144                                  _xml ("paragraph"));
145
146     xmlTextWriterWriteAttribute (w, _xml ("style:class"),
147                                  _xml ("text"));
148
149     xmlTextWriterEndElement (w); /* style:style */
150   }
151
152
153   {
154     xmlTextWriterStartElement (w, _xml ("style:style"));
155     xmlTextWriterWriteAttribute (w, _xml ("style:name"),
156                                  _xml ("Table_20_Contents"));
157
158     xmlTextWriterWriteAttribute (w, _xml ("style:display-name"),
159                                  _xml ("Table Contents"));
160
161     xmlTextWriterWriteAttribute (w, _xml ("style:family"),
162                                  _xml ("paragraph"));
163
164     xmlTextWriterWriteAttribute (w, _xml ("style:parent-style-name"),
165                                  _xml ("Standard"));
166
167     xmlTextWriterWriteAttribute (w, _xml ("style:class"),
168                                  _xml ("extra"));
169
170     xmlTextWriterEndElement (w); /* style:style */
171   }
172
173   {
174     xmlTextWriterStartElement (w, _xml ("style:style"));
175     xmlTextWriterWriteAttribute (w, _xml ("style:name"),
176                                  _xml ("Table_20_Heading"));
177
178     xmlTextWriterWriteAttribute (w, _xml ("style:display-name"),
179                                  _xml ("Table Heading"));
180
181     xmlTextWriterWriteAttribute (w, _xml ("style:family"),
182                                  _xml ("paragraph"));
183
184     xmlTextWriterWriteAttribute (w, _xml ("style:parent-style-name"),
185                                  _xml ("Table_20_Contents"));
186
187     xmlTextWriterWriteAttribute (w, _xml ("style:class"),
188                                  _xml ("extra"));
189
190
191     xmlTextWriterStartElement (w, _xml ("style:text-properties"));
192     xmlTextWriterWriteAttribute (w, _xml ("fo:font-weight"), _xml ("bold"));
193     xmlTextWriterWriteAttribute (w, _xml ("style:font-weight-asian"), _xml ("bold"));
194     xmlTextWriterWriteAttribute (w, _xml ("style:font-weight-complex"), _xml ("bold"));
195     xmlTextWriterEndElement (w); /* style:text-properties */
196
197     xmlTextWriterEndElement (w); /* style:style */
198   }
199
200   {
201     xmlTextWriterStartElement (w, _xml ("style:style"));
202     xmlTextWriterWriteAttribute (w, _xml ("style:name"), _xml ("superscript"));
203     xmlTextWriterWriteAttribute (w, _xml ("style:family"), _xml ("text"));
204
205     xmlTextWriterStartElement (w, _xml ("style:text-properties"));
206     xmlTextWriterWriteAttribute (w, _xml ("style:text-position"),
207                                  _xml ("super 58%"));
208     xmlTextWriterEndElement (w); /* style:text-properties */
209
210     xmlTextWriterEndElement (w); /* style:style */
211   }
212
213   xmlTextWriterEndElement (w); /* office:styles */
214   xmlTextWriterEndElement (w); /* office:document-styles */
215
216   xmlTextWriterEndDocument (w);
217   xmlFreeTextWriter (w);
218   zip_writer_add (odt->zip, file, "styles.xml");
219   close_temp_file (file);
220 }
221
222 static void
223 write_meta_data (struct odt_driver *odt)
224 {
225   xmlTextWriterPtr w;
226   FILE *file;
227
228   create_writer (&file, &w);
229   register_file (odt, "meta.xml");
230
231   xmlTextWriterStartElement (w, _xml ("office:document-meta"));
232   xmlTextWriterWriteAttribute (w, _xml ("xmlns:office"), _xml ("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
233   xmlTextWriterWriteAttribute (w, _xml ("xmlns:dc"),  _xml ("http://purl.org/dc/elements/1.1/"));
234   xmlTextWriterWriteAttribute (w, _xml ("xmlns:meta"), _xml ("urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
235   xmlTextWriterWriteAttribute (w, _xml ("xmlns:ooo"), _xml("http://openoffice.org/2004/office"));
236   xmlTextWriterWriteAttribute (w, _xml ("office:version"),  _xml("1.1"));
237
238   xmlTextWriterStartElement (w, _xml ("office:meta"));
239   {
240     xmlTextWriterStartElement (w, _xml ("meta:generator"));
241     xmlTextWriterWriteString (w, _xml (version));
242     xmlTextWriterEndElement (w);
243   }
244
245
246   {
247     char buf[30];
248     time_t t = time (NULL);
249     struct tm *tm =  localtime (&t);
250
251     strftime (buf, 30, "%Y-%m-%dT%H:%M:%S", tm);
252
253     xmlTextWriterStartElement (w, _xml ("meta:creation-date"));
254     xmlTextWriterWriteString (w, _xml (buf));
255     xmlTextWriterEndElement (w);
256
257     xmlTextWriterStartElement (w, _xml ("dc:date"));
258     xmlTextWriterWriteString (w, _xml (buf));
259     xmlTextWriterEndElement (w);
260   }
261
262 #ifdef HAVE_PWD_H
263   {
264     struct passwd *pw = getpwuid (getuid ());
265     if (pw != NULL)
266       {
267         xmlTextWriterStartElement (w, _xml ("meta:initial-creator"));
268         xmlTextWriterWriteString (w, _xml (strtok (pw->pw_gecos, ",")));
269         xmlTextWriterEndElement (w);
270
271         xmlTextWriterStartElement (w, _xml ("dc:creator"));
272         xmlTextWriterWriteString (w, _xml (strtok (pw->pw_gecos, ",")));
273         xmlTextWriterEndElement (w);
274       }
275   }
276 #endif
277
278   xmlTextWriterEndElement (w);
279   xmlTextWriterEndElement (w);
280   xmlTextWriterEndDocument (w);
281   xmlFreeTextWriter (w);
282   zip_writer_add (odt->zip, file, "meta.xml");
283   close_temp_file (file);
284 }
285
286 static struct output_driver *
287 odt_create (struct file_handle *fh, enum settings_output_devices device_type,
288             struct string_map *o UNUSED)
289 {
290   struct output_driver *d;
291   struct odt_driver *odt;
292   struct zip_writer *zip;
293   const char *file_name = fh_get_file_name (fh);
294
295   zip = zip_writer_create (file_name);
296   if (zip == NULL)
297     return NULL;
298
299   odt = xzalloc (sizeof *odt);
300   d = &odt->driver;
301
302   output_driver_init (d, &odt_driver_class, file_name, device_type);
303
304   odt->zip = zip;
305   odt->handle = fh;
306   odt->file_name = xstrdup (file_name);
307
308   zip_writer_add_string (zip, "mimetype",
309                          "application/vnd.oasis.opendocument.text");
310
311   /* Create the manifest */
312   create_writer (&odt->manifest_file, &odt->manifest_wtr);
313
314   xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:manifest"));
315   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("xmlns:manifest"),
316                                _xml("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"));
317
318
319   /* Add a manifest entry for the document as a whole */
320   xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:file-entry"));
321   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:media-type"),  _xml("application/vnd.oasis.opendocument.text"));
322   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:full-path"),  _xml("/"));
323   xmlTextWriterEndElement (odt->manifest_wtr);
324
325
326   write_meta_data (odt);
327   write_style_data (odt);
328
329   create_writer (&odt->content_file, &odt->content_wtr);
330   register_file (odt, "content.xml");
331
332
333   /* Some necessary junk at the start */
334   xmlTextWriterStartElement (odt->content_wtr, _xml("office:document-content"));
335   xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:office"),
336                                _xml("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
337
338   xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:text"),
339                                _xml("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
340
341   xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:table"),
342                                _xml("urn:oasis:names:tc:opendocument:xmlns:table:1.0"));
343
344   xmlTextWriterWriteAttribute (odt->content_wtr, _xml("office:version"), _xml("1.1"));
345
346   xmlTextWriterStartElement (odt->content_wtr, _xml("office:body"));
347   xmlTextWriterStartElement (odt->content_wtr, _xml("office:text"));
348
349
350
351   /* Close the manifest */
352   xmlTextWriterEndElement (odt->manifest_wtr);
353   xmlTextWriterEndDocument (odt->manifest_wtr);
354   xmlFreeTextWriter (odt->manifest_wtr);
355   zip_writer_add (odt->zip, odt->manifest_file, "META-INF/manifest.xml");
356   close_temp_file (odt->manifest_file);
357
358   return d;
359 }
360
361 static void
362 odt_destroy (struct output_driver *driver)
363 {
364   struct odt_driver *odt = odt_driver_cast (driver);
365
366   if (odt->content_wtr != NULL)
367     {
368       xmlTextWriterEndElement (odt->content_wtr); /* office:text */
369       xmlTextWriterEndElement (odt->content_wtr); /* office:body */
370       xmlTextWriterEndElement (odt->content_wtr); /* office:document-content */
371
372       xmlTextWriterEndDocument (odt->content_wtr);
373       xmlFreeTextWriter (odt->content_wtr);
374       zip_writer_add (odt->zip, odt->content_file, "content.xml");
375       close_temp_file (odt->content_file);
376
377       zip_writer_close (odt->zip);
378     }
379
380   fh_unref (odt->handle);
381   free (odt->file_name);
382   free (odt);
383 }
384
385 static void
386 write_xml_with_line_breaks (struct odt_driver *odt, const char *line_)
387 {
388   xmlTextWriterPtr writer = odt->content_wtr;
389
390   if (!strchr (line_, '\n'))
391     xmlTextWriterWriteString (writer, _xml(line_));
392   else
393     {
394       char *line = xstrdup (line_);
395       char *newline;
396       char *p;
397
398       for (p = line; *p; p = newline + 1)
399         {
400           newline = strchr (p, '\n');
401
402           if (!newline)
403             {
404               xmlTextWriterWriteString (writer, _xml(p));
405               free (line);
406               return;
407             }
408
409           if (newline > p && newline[-1] == '\r')
410             newline[-1] = '\0';
411           else
412             *newline = '\0';
413           xmlTextWriterWriteString (writer, _xml(p));
414           xmlTextWriterWriteElement (writer, _xml("text:line-break"), _xml(""));
415         }
416     }
417 }
418
419 static void
420 write_footnotes (struct odt_driver *odt, struct pivot_footnote **footnotes,
421                 size_t n_footnotes)
422 {
423   for (size_t i = 0; i < n_footnotes; i++)
424     {
425       xmlTextWriterStartElement (odt->content_wtr, _xml("text:span"));
426       xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"),
427                                    _xml("superscript"));
428       char *s = pivot_value_to_string (footnotes[i]->marker,
429                                        SETTINGS_VALUE_SHOW_DEFAULT,
430                                        SETTINGS_VALUE_SHOW_DEFAULT);
431       write_xml_with_line_breaks (odt, s);
432       free (s);
433       xmlTextWriterEndElement (odt->content_wtr);
434     }
435 }
436
437 static void
438 write_table_item_cell (struct odt_driver *odt,
439                        const struct table_cell *cell)
440 {
441   if (!cell)
442     return;
443
444   xmlTextWriterStartElement (odt->content_wtr, _xml("text:h"));
445   xmlTextWriterWriteFormatAttribute (odt->content_wtr,
446                                      _xml("text:outline-level"), "%d", 2);
447   xmlTextWriterWriteString (odt->content_wtr, _xml (cell->text));
448   write_footnotes (odt, cell->footnotes, cell->n_footnotes);
449   xmlTextWriterEndElement (odt->content_wtr);
450 }
451
452 static void
453 write_table__ (struct odt_driver *odt, const struct table *t)
454 {
455   if (t)
456     {
457       for (size_t y = 0; y < t->n[V]; y++)
458         {
459           struct table_cell cell;
460           table_get_cell (t, 0, y, &cell);
461           write_table_item_cell (odt, &cell);
462         }
463     }
464 }
465
466 static void
467 write_table_layer (struct odt_driver *odt, const struct pivot_table *pt,
468                    const size_t *layer_indexes)
469 {
470   struct table *title, *layers, *body, *caption, *footnotes;
471   pivot_output (pt, layer_indexes, true, &title, &layers, &body,
472                 &caption, &footnotes, NULL, NULL);
473
474   /* Write a heading for the table */
475   write_table__ (odt, title);
476   write_table__ (odt, layers);
477
478   /* Start table */
479   xmlTextWriterStartElement (odt->content_wtr, _xml("table:table"));
480   xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:name"),
481                                      "TABLE-%d", odt->table_num++);
482
483
484   /* Start column definitions */
485   xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-column"));
486   xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:number-columns-repeated"), "%d", body->n[H]);
487   xmlTextWriterEndElement (odt->content_wtr);
488
489
490   /* Deal with row headers */
491   if (body->h[V][0] > 0)
492     xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-header-rows"));
493
494
495   /* Write all the rows */
496   for (int r = 0 ; r < body->n[V]; ++r)
497     {
498       /* Start row definition */
499       xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-row"));
500
501       /* Write all the columns */
502       for (int c = 0 ; c < body->n[H] ; ++c)
503         {
504           struct table_cell cell;
505
506           table_get_cell (body, c, r, &cell);
507
508           if (c == cell.d[H][0] && r == cell.d[V][0])
509             {
510               int colspan = table_cell_colspan (&cell);
511               int rowspan = table_cell_rowspan (&cell);
512
513               xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-cell"));
514               xmlTextWriterWriteAttribute (odt->content_wtr, _xml("office:value-type"), _xml("string"));
515
516               if (colspan > 1)
517                 xmlTextWriterWriteFormatAttribute (
518                   odt->content_wtr, _xml("table:number-columns-spanned"),
519                   "%d", colspan);
520
521               if (rowspan > 1)
522                 xmlTextWriterWriteFormatAttribute (
523                   odt->content_wtr, _xml("table:number-rows-spanned"),
524                   "%d", rowspan);
525
526               xmlTextWriterStartElement (odt->content_wtr, _xml("text:p"));
527
528               if (r < body->h[V][0] || c < body->h[H][0])
529                 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Heading"));
530               else
531                 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Contents"));
532
533               if (cell.options & TAB_MARKUP)
534                 {
535                   /* XXX */
536                   char *s = output_get_text_from_markup (cell.text);
537                   write_xml_with_line_breaks (odt, s);
538                   free (s);
539                 }
540               else
541                 write_xml_with_line_breaks (odt, cell.text);
542
543               write_footnotes (odt, cell.footnotes, cell.n_footnotes);
544
545               xmlTextWriterEndElement (odt->content_wtr); /* text:p */
546               xmlTextWriterEndElement (odt->content_wtr); /* table:table-cell */
547             }
548           else
549             {
550               xmlTextWriterStartElement (odt->content_wtr, _xml("table:covered-table-cell"));
551               xmlTextWriterEndElement (odt->content_wtr);
552             }
553         }
554
555       xmlTextWriterEndElement (odt->content_wtr); /* row */
556
557       int ht = body->h[V][0];
558       if (ht > 0 && r == ht - 1)
559         xmlTextWriterEndElement (odt->content_wtr); /* table-header-rows */
560     }
561
562   xmlTextWriterEndElement (odt->content_wtr); /* table */
563
564   /* Write a caption for the table */
565   write_table__ (odt, caption);
566   write_table__ (odt, footnotes);
567
568   table_unref (title);
569   table_unref (layers);
570   table_unref (body);
571   table_unref (caption);
572   table_unref (footnotes);
573 }
574
575 static void
576 write_table (struct odt_driver *odt, const struct table_item *item)
577 {
578   size_t *layer_indexes;
579   PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
580     write_table_layer (odt, item->pt, layer_indexes);
581 }
582
583 static void
584 odt_output_text (struct odt_driver *odt, const char *text)
585 {
586   xmlTextWriterStartElement (odt->content_wtr, _xml("text:p"));
587   xmlTextWriterWriteString (odt->content_wtr, _xml(text));
588   xmlTextWriterEndElement (odt->content_wtr);
589 }
590
591 /* Submit a table to the ODT driver */
592 static void
593 odt_submit (struct output_driver *driver,
594             const struct output_item *output_item)
595 {
596   struct odt_driver *odt = odt_driver_cast (driver);
597
598   if (is_table_item (output_item))
599     write_table (odt, to_table_item (output_item));
600   else if (is_text_item (output_item))
601     odt_output_text (odt, text_item_get_text (to_text_item (output_item)));
602   else if (is_message_item (output_item))
603     {
604       const struct message_item *message_item = to_message_item (output_item);
605       char *s = msg_to_string (message_item_get_msg (message_item));
606       odt_output_text (odt, s);
607       free (s);
608     }
609 }
610
611 struct output_driver_factory odt_driver_factory =
612   { "odt", "pspp.odf", odt_create };
613
614 static const struct output_driver_class odt_driver_class =
615 {
616   "odf",
617   odt_destroy,
618   odt_submit,
619   NULL,
620 };