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