Greatly simplify PSPP configuration.
[pspp-builds.git] / src / output / odt.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010 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 #include <pwd.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <time.h>
28 #include <unistd.h>
29
30 #include "libpspp/assertion.h"
31 #include "libpspp/cast.h"
32 #include "libpspp/str.h"
33 #include "libpspp/version.h"
34 #include "output/driver-provider.h"
35 #include "output/options.h"
36 #include "output/tab.h"
37 #include "output/table-item.h"
38 #include "output/table-provider.h"
39 #include "output/text-item.h"
40
41 #include "gl/xalloc.h"
42 #include "gl/error.h"
43
44 #include "gettext.h"
45 #define _(msgid) gettext (msgid)
46
47 #define _xml(X) (const xmlChar *)(X)
48
49 struct odt_driver
50 {
51   struct output_driver driver;
52
53   char *file_name;            /* Output file name. */
54   bool debug;
55
56   /* The name of the temporary directory used to construct the ODF */
57   char *dirname;
58
59   /* Writer for the content.xml file */
60   xmlTextWriterPtr content_wtr;
61
62   /* Writer fot the manifest.xml file */
63   xmlTextWriterPtr manifest_wtr;
64
65   /* Number of tables so far. */
66   int table_num;
67 };
68
69 static const struct output_driver_class odt_driver_class;
70
71 static struct odt_driver *
72 odt_driver_cast (struct output_driver *driver)
73 {
74   assert (driver->class == &odt_driver_class);
75   return UP_CAST (driver, struct odt_driver, driver);
76 }
77
78 /* Create the "mimetype" file needed by ODF */
79 static bool
80 create_mimetype (const char *dirname)
81 {
82   FILE *fp;
83   struct string filename;
84   ds_init_cstr (&filename, dirname);
85   ds_put_cstr (&filename, "/mimetype");
86   fp = fopen (ds_cstr (&filename), "w");
87
88   if (fp == NULL)
89     {
90       error (0, errno, _("failed to create output file %s"),
91              ds_cstr (&filename));
92       ds_destroy (&filename);
93       return false;
94     }
95   ds_destroy (&filename);
96
97   fprintf (fp, "application/vnd.oasis.opendocument.text");
98   fclose (fp);
99
100   return true;
101 }
102
103 /* Create a new XML file called FILENAME in the temp directory, and return a writer for it */
104 static xmlTextWriterPtr
105 create_writer (const struct odt_driver *driver, const char *filename)
106 {
107   char *copy = NULL;
108   xmlTextWriterPtr w;
109   struct string str;
110   ds_init_cstr (&str, driver->dirname);
111   ds_put_cstr (&str, "/");
112   ds_put_cstr (&str, filename);
113
114   /* dirname modifies its argument, so we must copy it */
115   copy = xstrdup (ds_cstr (&str));
116   mkdir (dirname (copy), 0700);
117   free (copy);
118
119   w = xmlNewTextWriterFilename (ds_cstr (&str), 0);
120
121   ds_destroy (&str);
122
123   xmlTextWriterStartDocument (w, NULL, "UTF-8", NULL);
124
125   return w;
126 }
127
128
129 static void
130 register_file (struct odt_driver *odt, const char *filename)
131 {
132   assert (odt->manifest_wtr);
133   xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:file-entry"));
134   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:media-type"),  _xml("text/xml"));
135   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:full-path"),  _xml (filename));
136   xmlTextWriterEndElement (odt->manifest_wtr);
137 }
138
139 static void
140 write_style_data (struct odt_driver *odt)
141 {
142   xmlTextWriterPtr w = create_writer (odt, "styles.xml");
143   register_file (odt, "styles.xml");
144
145   xmlTextWriterStartElement (w, _xml ("office:document-styles"));
146   xmlTextWriterWriteAttribute (w, _xml ("xmlns:office"),
147                                _xml ("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
148
149   xmlTextWriterWriteAttribute (w, _xml ("xmlns:style"),
150                                _xml ("urn:oasis:names:tc:opendocument:xmlns:style:1.0"));
151
152   xmlTextWriterWriteAttribute (w, _xml ("xmlns:fo"),
153                                _xml ("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0") );
154
155   xmlTextWriterWriteAttribute (w, _xml ("office:version"),  _xml ("1.1"));
156                                
157
158
159   xmlTextWriterStartElement (w, _xml ("office:styles"));
160
161
162   {
163     xmlTextWriterStartElement (w, _xml ("style:style"));
164     xmlTextWriterWriteAttribute (w, _xml ("style:name"),
165                                  _xml ("Standard"));
166
167     xmlTextWriterWriteAttribute (w, _xml ("style:family"),
168                                  _xml ("paragraph"));
169
170     xmlTextWriterWriteAttribute (w, _xml ("style:class"),
171                                  _xml ("text"));
172
173     xmlTextWriterEndElement (w); /* style:style */
174   }
175
176   {
177     xmlTextWriterStartElement (w, _xml ("style:style"));
178     xmlTextWriterWriteAttribute (w, _xml ("style:name"),
179                                  _xml ("Table_20_Contents"));
180
181     xmlTextWriterWriteAttribute (w, _xml ("style:display-name"),
182                                  _xml ("Table Contents"));
183
184     xmlTextWriterWriteAttribute (w, _xml ("style:family"),
185                                  _xml ("paragraph"));
186
187     xmlTextWriterWriteAttribute (w, _xml ("style:parent-style-name"),
188                                  _xml ("Standard"));
189
190     xmlTextWriterWriteAttribute (w, _xml ("style:class"),
191                                  _xml ("extra"));
192
193     xmlTextWriterEndElement (w); /* style:style */
194   }
195
196   {
197     xmlTextWriterStartElement (w, _xml ("style:style"));
198     xmlTextWriterWriteAttribute (w, _xml ("style:name"),
199                                  _xml ("Table_20_Heading"));
200
201     xmlTextWriterWriteAttribute (w, _xml ("style:display-name"),
202                                  _xml ("Table Heading"));
203
204     xmlTextWriterWriteAttribute (w, _xml ("style:family"),
205                                  _xml ("paragraph"));
206
207     xmlTextWriterWriteAttribute (w, _xml ("style:parent-style-name"),
208                                  _xml ("Table_20_Contents"));
209
210     xmlTextWriterWriteAttribute (w, _xml ("style:class"),
211                                  _xml ("extra"));
212
213
214     xmlTextWriterStartElement (w, _xml ("style:text-properties"));
215     xmlTextWriterWriteAttribute (w, _xml ("fo:font-weight"), _xml ("bold"));
216     xmlTextWriterWriteAttribute (w, _xml ("style:font-weight-asian"), _xml ("bold"));
217     xmlTextWriterWriteAttribute (w, _xml ("style:font-weight-complex"), _xml ("bold"));
218     xmlTextWriterEndElement (w); /* style:text-properties */
219
220     xmlTextWriterEndElement (w); /* style:style */
221   }
222
223
224   xmlTextWriterEndElement (w); /* office:styles */
225   xmlTextWriterEndElement (w); /* office:document-styles */
226
227   xmlTextWriterEndDocument (w);
228   xmlFreeTextWriter (w);
229 }
230
231 static void
232 write_meta_data (struct odt_driver *odt)
233 {
234   xmlTextWriterPtr w = create_writer (odt, "meta.xml");
235   register_file (odt, "meta.xml");
236
237   xmlTextWriterStartElement (w, _xml ("office:document-meta"));
238   xmlTextWriterWriteAttribute (w, _xml ("xmlns:office"), _xml ("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
239   xmlTextWriterWriteAttribute (w, _xml ("xmlns:dc"),  _xml ("http://purl.org/dc/elements/1.1/"));
240   xmlTextWriterWriteAttribute (w, _xml ("xmlns:meta"), _xml ("urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
241   xmlTextWriterWriteAttribute (w, _xml ("xmlns:ooo"), _xml("http://openoffice.org/2004/office"));
242   xmlTextWriterWriteAttribute (w, _xml ("office:version"),  _xml("1.1"));
243
244   xmlTextWriterStartElement (w, _xml ("office:meta"));
245   {
246     xmlTextWriterStartElement (w, _xml ("meta:generator"));
247     xmlTextWriterWriteString (w, _xml (stat_version));
248     xmlTextWriterEndElement (w);
249   }
250
251
252   {
253     char buf[30];
254     struct passwd *pw = getpwuid (getuid ());
255     time_t t = time (NULL);
256     struct tm *tm =  localtime (&t);
257
258     strftime (buf, 30, "%Y-%m-%dT%H:%M:%S", tm);
259
260     xmlTextWriterStartElement (w, _xml ("meta:initial-creator"));
261     xmlTextWriterWriteString (w, _xml (strtok (pw->pw_gecos, ",")));
262     xmlTextWriterEndElement (w);
263
264     xmlTextWriterStartElement (w, _xml ("meta:creation-date"));
265     xmlTextWriterWriteString (w, _xml (buf));
266     xmlTextWriterEndElement (w);
267
268     xmlTextWriterStartElement (w, _xml ("dc:creator"));
269     xmlTextWriterWriteString (w, _xml (strtok (pw->pw_gecos, ",")));
270
271     xmlTextWriterEndElement (w);
272
273     xmlTextWriterStartElement (w, _xml ("dc:date"));
274     xmlTextWriterWriteString (w, _xml (buf));
275     xmlTextWriterEndElement (w);
276   }
277
278   xmlTextWriterEndElement (w);
279   xmlTextWriterEndElement (w);
280   xmlTextWriterEndDocument (w);
281   xmlFreeTextWriter (w);
282 }
283
284 enum
285 {
286   output_file_arg,
287   boolean_arg,
288 };
289
290 static struct driver_option *
291 opt (struct output_driver *d, struct string_map *options, const char *key,
292      const char *default_value)
293 {
294   return driver_option_get (d, options, key, default_value);
295 }
296
297 static struct output_driver *
298 odt_create (const char *file_name, enum settings_output_devices device_type,
299             struct string_map *o)
300 {
301   struct output_driver *d;
302   struct odt_driver *odt;
303
304   odt = xzalloc (sizeof *odt);
305   d = &odt->driver;
306   output_driver_init (d, &odt_driver_class, file_name, device_type);
307
308   odt->file_name = xstrdup (file_name);
309   odt->debug = parse_boolean (opt (d, o, "debug", "false"));
310
311   odt->dirname = xstrdup ("odt-XXXXXX");
312   mkdtemp (odt->dirname);
313
314   if (!create_mimetype (odt->dirname))
315     {
316       output_driver_destroy (d);
317       return NULL;
318     }
319
320   /* Create the manifest */
321   odt->manifest_wtr = create_writer (odt, "META-INF/manifest.xml");
322
323   xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:manifest"));
324   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("xmlns:manifest"),
325                                _xml("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"));
326
327
328   /* Add a manifest entry for the document as a whole */
329   xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:file-entry"));
330   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:media-type"),  _xml("application/vnd.oasis.opendocument.text"));
331   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:full-path"),  _xml("/"));
332   xmlTextWriterEndElement (odt->manifest_wtr);
333
334
335   write_meta_data (odt);
336   write_style_data (odt);
337
338   odt->content_wtr = create_writer (odt, "content.xml");
339   register_file (odt, "content.xml");
340
341
342   /* Some necessary junk at the start */
343   xmlTextWriterStartElement (odt->content_wtr, _xml("office:document-content"));
344   xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:office"),
345                                _xml("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
346
347   xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:text"),
348                                _xml("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
349
350   xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:table"),
351                                _xml("urn:oasis:names:tc:opendocument:xmlns:table:1.0"));
352
353   xmlTextWriterWriteAttribute (odt->content_wtr, _xml("office:version"), _xml("1.1"));
354
355   xmlTextWriterStartElement (odt->content_wtr, _xml("office:body"));
356   xmlTextWriterStartElement (odt->content_wtr, _xml("office:text"));
357
358
359
360   /* Close the manifest */
361   xmlTextWriterEndElement (odt->manifest_wtr);
362   xmlTextWriterEndDocument (odt->manifest_wtr);
363   xmlFreeTextWriter (odt->manifest_wtr);
364
365   return d;
366 }
367
368 static void
369 odt_destroy (struct output_driver *driver)
370 {
371   struct odt_driver *odt = odt_driver_cast (driver);
372
373   if (odt->content_wtr != NULL)
374     {
375       struct string zip_cmd;
376
377       xmlTextWriterEndElement (odt->content_wtr); /* office:text */
378       xmlTextWriterEndElement (odt->content_wtr); /* office:body */
379       xmlTextWriterEndElement (odt->content_wtr); /* office:document-content */
380
381       xmlTextWriterEndDocument (odt->content_wtr);
382       xmlFreeTextWriter (odt->content_wtr);
383
384       /* Zip up the directory */
385       ds_init_empty (&zip_cmd);
386       ds_put_format (&zip_cmd,
387                      "cd %s ; rm -f ../%s; zip -q -X ../%s mimetype; zip -q -X -u -r ../%s .",
388                      odt->dirname, odt->file_name, odt->file_name, odt->file_name);
389       system (ds_cstr (&zip_cmd));
390       ds_destroy (&zip_cmd);
391     }
392
393   if ( !odt->debug )
394     {
395       /* Remove the temp dir */
396       struct string rm_cmd;
397
398       ds_init_empty (&rm_cmd);
399       ds_put_format (&rm_cmd, "rm -r %s", odt->dirname);
400       system (ds_cstr (&rm_cmd));
401       ds_destroy (&rm_cmd);
402     }
403   else
404     fprintf (stderr, "Not removing directory %s\n", odt->dirname);
405
406   free (odt->dirname);
407   free (odt);
408 }
409
410 static void
411 odt_submit_table (struct odt_driver *odt, struct table_item *item)
412 {
413   const struct table *tab = table_item_get_table (item);
414   const char *caption = table_item_get_caption (item);
415   int r, c;
416
417   /* Write a heading for the table */
418   if (caption != NULL)
419     {
420       xmlTextWriterStartElement (odt->content_wtr, _xml("text:h"));
421       xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("text:level"),
422                                          "%d", 2);
423       xmlTextWriterWriteString (odt->content_wtr,
424                                 _xml (table_item_get_caption (item)) );
425       xmlTextWriterEndElement (odt->content_wtr);
426     }
427
428   /* Start table */
429   xmlTextWriterStartElement (odt->content_wtr, _xml("table:table"));
430   xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:name"), 
431                                      "TABLE-%d", odt->table_num++);
432
433
434   /* Start column definitions */
435   xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-column"));
436   xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:number-columns-repeated"), "%d", table_nc (tab));
437   xmlTextWriterEndElement (odt->content_wtr);
438
439
440   /* Deal with row headers */
441   if ( table_ht (tab) > 0)
442     xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-header-rows"));
443     
444
445   /* Write all the rows */
446   for (r = 0 ; r < table_nr (tab); ++r)
447     {
448       /* Start row definition */
449       xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-row"));
450
451       /* Write all the columns */
452       for (c = 0 ; c < table_nc (tab) ; ++c)
453         {
454           struct table_cell cell;
455
456           table_get_cell (tab, c, r, &cell);
457
458           if (c == cell.d[TABLE_HORZ][0] && r == cell.d[TABLE_VERT][0])
459             {
460               int colspan = table_cell_colspan (&cell);
461               int rowspan = table_cell_rowspan (&cell);
462
463               xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-cell"));
464               xmlTextWriterWriteAttribute (odt->content_wtr, _xml("office:value-type"), _xml("string"));
465
466               if (colspan > 1)
467                 xmlTextWriterWriteFormatAttribute (
468                   odt->content_wtr, _xml("table:number-columns-spanned"),
469                   "%d", colspan);
470
471               if (rowspan > 1)
472                 xmlTextWriterWriteFormatAttribute (
473                   odt->content_wtr, _xml("table:number-rows-spanned"),
474                   "%d", rowspan);
475
476               xmlTextWriterStartElement (odt->content_wtr, _xml("text:p"));
477
478               if ( r < table_ht (tab) || c < table_hl (tab) )
479                 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Heading"));
480               else
481                 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Contents"));
482
483               xmlTextWriterWriteString (odt->content_wtr, _xml(cell.contents));
484
485               xmlTextWriterEndElement (odt->content_wtr); /* text:p */
486               xmlTextWriterEndElement (odt->content_wtr); /* table:table-cell */
487             }
488           else
489             {
490               xmlTextWriterStartElement (odt->content_wtr, _xml("table:covered-table-cell"));
491               xmlTextWriterEndElement (odt->content_wtr);
492             }
493
494           table_cell_free (&cell);
495         }
496   
497       xmlTextWriterEndElement (odt->content_wtr); /* row */
498
499       if ( table_ht (tab) > 0 && r == table_ht (tab) - 1)
500         xmlTextWriterEndElement (odt->content_wtr); /* table-header-rows */
501     }
502
503   xmlTextWriterEndElement (odt->content_wtr); /* table */
504 }
505
506 /* Submit a table to the ODT driver */
507 static void
508 odt_submit (struct output_driver *driver,
509             const struct output_item *output_item)
510 {
511   struct odt_driver *odt = odt_driver_cast (driver);
512   if (is_table_item (output_item))
513     odt_submit_table (odt, to_table_item (output_item));
514   else if (is_text_item (output_item))
515     {
516       const struct text_item *text_item = to_text_item (output_item);
517       const char *text = text_item_get_text (text_item);
518
519       /* XXX apply different styles based on text_item's type.  */
520       xmlTextWriterStartElement (odt->content_wtr, _xml("text:p"));
521       xmlTextWriterWriteString (odt->content_wtr, _xml(text));
522       xmlTextWriterEndElement (odt->content_wtr);
523     }
524 }
525
526 struct output_driver_factory odt_driver_factory = { "odt", odt_create };
527
528 static const struct output_driver_class odt_driver_class =
529 {
530   "odf",
531   odt_destroy,
532   odt_submit,
533   NULL,
534 };