Prevent existing output file from interfereing with zip
[pspp] / 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
20 /* A driver for creating OpenDocument Format text files from PSPP's output */
21
22 #include <libpspp/assertion.h>
23 #include <libpspp/version.h>
24
25 #include <output/manager.h>
26 #include <output/output.h>
27 #include <output/table.h>
28
29 #include <time.h>
30 #include <pwd.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33
34 #include <libgen.h>
35
36 #include <libxml/xmlwriter.h>
37
38 #include "xalloc.h"
39
40 #define _xml(X) (const xmlChar *)(X)
41
42 struct odt_driver_ext 
43 {
44   /* The name of the temporary directory used to construct the ODF */
45   char *dirname;
46
47   /* Writer for the content.xml file */
48   xmlTextWriterPtr content_wtr;
49
50   /* Writer fot the manifest.xml file */
51   xmlTextWriterPtr manifest_wtr;
52 };
53
54
55
56 /* Create the "mimetype" file needed by ODF */
57 static void
58 create_mimetype (const char *dirname)
59 {
60   FILE *fp;
61   struct string filename;
62   ds_init_cstr (&filename, dirname);
63   ds_put_cstr (&filename, "/mimetype");
64   fp = fopen (ds_cstr (&filename), "w");
65   ds_destroy (&filename);
66
67   assert (fp);
68   fprintf (fp, "application/vnd.oasis.opendocument.text");
69   fclose (fp);
70 }
71
72 /* Create a new XML file called FILENAME in the temp directory, and return a writer for it */
73 static xmlTextWriterPtr
74 create_writer (const struct odt_driver_ext *driver, const char *filename)
75 {
76   char *copy = NULL;
77   xmlTextWriterPtr w;
78   struct string str;
79   ds_init_cstr (&str, driver->dirname);
80   ds_put_cstr (&str, "/");
81   ds_put_cstr (&str, filename);
82
83   /* dirname modifies its argument, so we must copy it */
84   copy = xstrdup (ds_cstr (&str));
85   mkdir (dirname (copy), 0700);
86   free (copy);
87
88   w = xmlNewTextWriterFilename (ds_cstr (&str), 0);
89
90   ds_destroy (&str);
91
92   xmlTextWriterStartDocument (w, NULL, "UTF-8", NULL);
93
94   return w;
95 }
96
97
98 static void
99 register_file (struct odt_driver_ext *x, const char *filename)
100 {
101   assert (x->manifest_wtr);
102   xmlTextWriterStartElement (x->manifest_wtr, _xml("manifest:file-entry"));
103   xmlTextWriterWriteAttribute (x->manifest_wtr, _xml("manifest:media-type"),  _xml("text/xml"));
104   xmlTextWriterWriteAttribute (x->manifest_wtr, _xml("manifest:full-path"),  _xml (filename));
105   xmlTextWriterEndElement (x->manifest_wtr);
106 }
107
108 static void
109 write_meta_data (struct odt_driver_ext *x)
110 {
111   xmlTextWriterPtr w = create_writer (x, "meta.xml");
112   register_file (x, "meta.xml");
113
114   xmlTextWriterStartElement (w, _xml ("office:document-meta"));
115   xmlTextWriterWriteAttribute (w, _xml ("xmlns:office"), _xml ("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
116   xmlTextWriterWriteAttribute (w, _xml ("xmlns:dc"),  _xml ("http://purl.org/dc/elements/1.1/"));
117   xmlTextWriterWriteAttribute (w, _xml ("xmlns:meta"), _xml ("urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
118   xmlTextWriterWriteAttribute (w, _xml ("xmlns:ooo"), _xml("http://openoffice.org/2004/office"));
119   xmlTextWriterWriteAttribute (w, _xml ("office:version"),  _xml("1.1"));
120
121   xmlTextWriterStartElement (w, _xml ("office:meta"));
122   {
123     xmlTextWriterStartElement (w, _xml ("meta:generator"));
124     xmlTextWriterWriteString (w, _xml (stat_version));
125     xmlTextWriterEndElement (w);
126   }
127
128
129   {
130     char buf[30];
131     struct passwd *pw = getpwuid (getuid ());
132     time_t t = time (NULL);
133     struct tm *tm =  localtime (&t);
134
135     strftime (buf, 30, "%Y-%m-%dT%H:%M:%S", tm);
136
137     xmlTextWriterStartElement (w, _xml ("meta:initial-creator"));
138     xmlTextWriterWriteString (w, _xml (strtok (pw->pw_gecos, ",")));
139     xmlTextWriterEndElement (w);
140
141     xmlTextWriterStartElement (w, _xml ("meta:creation-date"));
142     xmlTextWriterWriteString (w, _xml (buf));
143     xmlTextWriterEndElement (w);
144
145     xmlTextWriterStartElement (w, _xml ("dc:creator"));
146     xmlTextWriterWriteString (w, _xml (strtok (pw->pw_gecos, ",")));
147
148     xmlTextWriterEndElement (w);
149
150     xmlTextWriterStartElement (w, _xml ("dc:date"));
151     xmlTextWriterWriteString (w, _xml (buf));
152     xmlTextWriterEndElement (w);
153   }
154
155   xmlTextWriterEndElement (w);
156   xmlTextWriterEndElement (w);
157   xmlTextWriterEndDocument (w);
158   xmlFreeTextWriter (w);
159 }
160
161 static bool
162 odt_open_driver (const char *name, int types, struct substring option_string)
163 {
164   struct odt_driver_ext *x;
165   struct outp_driver *this = outp_allocate_driver (&odt_class, name, types);
166
167   this->ext = x = xmalloc (sizeof *x);
168
169   outp_register_driver (this);
170
171   x->dirname = xstrdup ("odt-XXXXXX");
172   mkdtemp (x->dirname);
173
174   create_mimetype (x->dirname);
175
176   /* Create the manifest */
177   x->manifest_wtr = create_writer (x, "META-INF/manifest.xml");
178
179   xmlTextWriterStartElement (x->manifest_wtr, _xml("manifest:manifest"));
180   xmlTextWriterWriteAttribute (x->manifest_wtr, _xml("xmlns:manifest"),
181                                _xml("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"));
182
183
184   /* Add a manifest entry for the document as a whole */
185   xmlTextWriterStartElement (x->manifest_wtr, _xml("manifest:file-entry"));
186   xmlTextWriterWriteAttribute (x->manifest_wtr, _xml("manifest:media-type"),  _xml("application/vnd.oasis.opendocument.text"));
187   xmlTextWriterWriteAttribute (x->manifest_wtr, _xml("manifest:full-path"),  _xml("/"));
188   xmlTextWriterEndElement (x->manifest_wtr);
189
190
191   write_meta_data (x);
192
193   x->content_wtr = create_writer (x, "content.xml");
194   register_file (x, "content.xml");
195
196
197   /* Some necessary junk at the start */
198   xmlTextWriterStartElement (x->content_wtr, _xml("office:document-content"));
199   xmlTextWriterWriteAttribute (x->content_wtr, _xml("xmlns:office"),
200                                _xml("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
201
202   xmlTextWriterWriteAttribute (x->content_wtr, _xml("xmlns:text"),
203                                _xml("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
204
205   xmlTextWriterWriteAttribute (x->content_wtr, _xml("xmlns:table"),
206                                _xml("urn:oasis:names:tc:opendocument:xmlns:table:1.0"));
207
208   xmlTextWriterWriteAttribute (x->content_wtr, _xml("office:version"), _xml("1.1"));
209
210   xmlTextWriterStartElement (x->content_wtr, _xml("office:body"));
211   xmlTextWriterStartElement (x->content_wtr, _xml("office:text"));
212
213
214
215   /* Close the manifest */
216   xmlTextWriterEndElement (x->manifest_wtr);
217   xmlTextWriterEndDocument (x->manifest_wtr);
218   xmlFreeTextWriter (x->manifest_wtr);
219
220   return true;
221 }
222
223 static bool
224 odt_close_driver (struct outp_driver *this)
225 {
226   struct string zip_cmd;
227   struct string rm_cmd;
228   struct odt_driver_ext *x = this->ext;
229
230   xmlTextWriterEndElement (x->content_wtr); /* office:text */
231   xmlTextWriterEndElement (x->content_wtr); /* office:body */
232   xmlTextWriterEndElement (x->content_wtr); /* office:document-content */
233
234   xmlTextWriterEndDocument (x->content_wtr);
235   xmlFreeTextWriter (x->content_wtr);
236
237   /* Zip up the directory */
238   ds_init_empty (&zip_cmd);
239   ds_put_format (&zip_cmd, "cd %s ; rm -f ../pspp.odt; zip -q -X ../pspp.odt mimetype; zip -q -X -u -r ../pspp.odt .", x->dirname);
240   system (ds_cstr (&zip_cmd));
241   ds_destroy (&zip_cmd);
242
243
244   /* Remove the temp dir */
245   ds_init_empty (&rm_cmd);
246   ds_put_format (&rm_cmd, "rm -r %s", x->dirname);
247   system (ds_cstr (&rm_cmd));
248   ds_destroy (&rm_cmd);
249
250   free (x->dirname);
251   free (x);
252
253   return true;
254 }
255
256 static void
257 odt_open_page (struct outp_driver *this)
258 {
259 }
260
261 static void
262 odt_close_page (struct outp_driver *this)
263 {
264 }
265
266 static void
267 odt_output_chart (struct outp_driver *this, const struct chart *chart)
268 {
269  printf ("%s\n", __FUNCTION__);
270 }
271
272
273 /* Submit a table to the ODT driver */
274 static void
275 odt_submit (struct outp_driver *this, struct som_entity *e)
276 {
277   int r, c;
278   
279   struct odt_driver_ext *x = this->ext;
280   struct tab_table *tab = e->ext;
281
282
283   /* Write a heading for the table */
284   xmlTextWriterStartElement (x->content_wtr, _xml("text:h"));
285   xmlTextWriterWriteFormatAttribute (x->content_wtr, _xml("text:level"), "%d", e->subtable_num == 1 ? 2 : 3);
286   xmlTextWriterWriteString (x->content_wtr, _xml (tab->title) );
287   xmlTextWriterEndElement (x->content_wtr);
288
289   /* Start table */
290   xmlTextWriterStartElement (x->content_wtr, _xml("table:table"));
291   xmlTextWriterWriteFormatAttribute (x->content_wtr, _xml("table:name"), 
292                                      "TABLE-%d.%d", e->table_num, e->subtable_num);
293
294
295   /* Start column definitions */
296   xmlTextWriterStartElement (x->content_wtr, _xml("table:table-column"));
297   xmlTextWriterWriteFormatAttribute (x->content_wtr, _xml("table:number-columns-repeated"), "%d", tab->nc);
298   xmlTextWriterEndElement (x->content_wtr);
299
300
301   /* Deal with row headers */
302   if ( tab->t > 0)
303     xmlTextWriterStartElement (x->content_wtr, _xml("table:table-header-rows"));
304     
305
306   /* Write all the rows */
307   for (r = 0 ; r < tab->nr; ++r)
308     {
309       int spanned_columns = 0;
310       /* Start row definition */
311       xmlTextWriterStartElement (x->content_wtr, _xml("table:table-row"));
312
313
314       /* Write all the columns */
315       for (c = 0 ; c < tab->nc ; ++c)
316         {
317           char *s = NULL;
318           unsigned int opts = tab->ct[tab->nc * r + c];
319           struct substring ss = tab->cc[tab->nc * r + c];
320
321           if (opts & TAB_EMPTY)
322             {
323               xmlTextWriterStartElement (x->content_wtr, _xml("table:table-cell"));
324               xmlTextWriterEndElement (x->content_wtr);
325               continue;
326             }
327
328           if ( opts & TAB_JOIN)
329             {
330               if ( spanned_columns == 0)
331                 {
332                   struct tab_joined_cell *j = (struct tab_joined_cell*) ss_data (ss);
333                   s = ss_xstrdup (j->contents);
334                 }
335             }
336           else
337             s = ss_xstrdup (ss);
338
339           if ( spanned_columns == 0 )
340             {
341               xmlTextWriterStartElement (x->content_wtr, _xml("table:table-cell"));
342               xmlTextWriterWriteAttribute (x->content_wtr, _xml("office:value-type"), _xml("string"));
343
344               if ( opts & TAB_JOIN )
345                 {
346                   struct tab_joined_cell *j = (struct tab_joined_cell*) ss_data (ss);
347                   spanned_columns = j->x2 - j->x1;
348
349                   xmlTextWriterWriteFormatAttribute (x->content_wtr,
350                                                      _xml("table:number-columns-spanned"),
351                                                      "%d", spanned_columns);
352                 }
353
354               xmlTextWriterStartElement (x->content_wtr, _xml("text:p"));
355
356               if ( r < tab->t || c < tab->l )
357                 xmlTextWriterWriteAttribute (x->content_wtr, _xml("text:style-name"), _xml("Table_20_Heading"));
358               else
359                 xmlTextWriterWriteAttribute (x->content_wtr, _xml("text:style-name"), _xml("Table_20_Contents"));
360
361               xmlTextWriterWriteString (x->content_wtr, _xml (s));
362           
363               xmlTextWriterEndElement (x->content_wtr); /* text:p */
364               xmlTextWriterEndElement (x->content_wtr); /* table:table-cell */
365             }
366           else
367             {
368               xmlTextWriterStartElement (x->content_wtr, _xml("table:covered-table-cell"));
369               xmlTextWriterEndElement (x->content_wtr);
370               spanned_columns --;
371             }
372
373           free (s);
374         }
375   
376       xmlTextWriterEndElement (x->content_wtr); /* row */
377
378       if ( tab->t > 0 && r == tab->t - 1)
379         xmlTextWriterEndElement (x->content_wtr); /* table-header-rows */
380     }
381
382   xmlTextWriterEndElement (x->content_wtr); /* table */
383 }
384
385
386 /* ODT driver class. */
387 const struct outp_class odt_class =
388 {
389   "odf",
390   1,
391
392   odt_open_driver,
393   odt_close_driver,
394
395   odt_open_page,
396   odt_close_page,
397   NULL,
398
399   odt_output_chart,
400   odt_submit,
401
402   NULL,
403   NULL,
404   NULL,
405 };