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