1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
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.
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.
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/>. */
19 /* A driver for creating OpenDocument Format text files from PSPP's output */
23 #include <libxml/xmlwriter.h>
28 #include <sys/types.h>
32 #include "libpspp/assertion.h"
33 #include "libpspp/cast.h"
34 #include "libpspp/str.h"
35 #include "libpspp/temp-file.h"
36 #include "libpspp/version.h"
37 #include "libpspp/zip-writer.h"
38 #include "output/driver-provider.h"
39 #include "output/message-item.h"
40 #include "output/options.h"
41 #include "output/tab.h"
42 #include "output/table-item.h"
43 #include "output/table-provider.h"
44 #include "output/text-item.h"
46 #include "gl/xalloc.h"
50 #define _(msgid) gettext (msgid)
52 #define _xml(X) (CHAR_CAST (const xmlChar *, X))
56 struct output_driver driver;
58 struct zip_writer *zip; /* ZIP file writer. */
59 char *file_name; /* Output file name. */
62 xmlTextWriterPtr content_wtr; /* XML writer. */
63 FILE *content_file; /* Temporary file. */
66 xmlTextWriterPtr manifest_wtr; /* XML writer. */
67 FILE *manifest_file; /* Temporary file. */
69 /* Number of tables so far. */
72 /* Name of current command. */
76 static const struct output_driver_class odt_driver_class;
78 static struct odt_driver *
79 odt_driver_cast (struct output_driver *driver)
81 assert (driver->class == &odt_driver_class);
82 return UP_CAST (driver, struct odt_driver, driver);
85 /* Create the "mimetype" file needed by ODF */
87 create_mimetype (struct zip_writer *zip)
91 fp = create_temp_file ();
94 error (0, errno, _("error creating temporary file"));
98 fprintf (fp, "application/vnd.oasis.opendocument.text");
99 zip_writer_add (zip, fp, "mimetype");
100 close_temp_file (fp);
105 /* Creates a new temporary file and stores it in *FILE, then creates an XML
106 writer for it and stores it in *W. */
108 create_writer (FILE **file, xmlTextWriterPtr *w)
110 /* XXX this can fail */
111 *file = create_temp_file ();
112 *w = xmlNewTextWriter (xmlOutputBufferCreateFile (*file, NULL));
114 xmlTextWriterStartDocument (*w, NULL, "UTF-8", NULL);
119 register_file (struct odt_driver *odt, const char *filename)
121 assert (odt->manifest_wtr);
122 xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:file-entry"));
123 xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:media-type"), _xml("text/xml"));
124 xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:full-path"), _xml (filename));
125 xmlTextWriterEndElement (odt->manifest_wtr);
129 write_style_data (struct odt_driver *odt)
134 create_writer (&file, &w);
135 register_file (odt, "styles.xml");
137 xmlTextWriterStartElement (w, _xml ("office:document-styles"));
138 xmlTextWriterWriteAttribute (w, _xml ("xmlns:office"),
139 _xml ("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
141 xmlTextWriterWriteAttribute (w, _xml ("xmlns:style"),
142 _xml ("urn:oasis:names:tc:opendocument:xmlns:style:1.0"));
144 xmlTextWriterWriteAttribute (w, _xml ("xmlns:fo"),
145 _xml ("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0") );
147 xmlTextWriterWriteAttribute (w, _xml ("office:version"), _xml ("1.1"));
151 xmlTextWriterStartElement (w, _xml ("office:styles"));
155 xmlTextWriterStartElement (w, _xml ("style:style"));
156 xmlTextWriterWriteAttribute (w, _xml ("style:name"),
159 xmlTextWriterWriteAttribute (w, _xml ("style:family"),
162 xmlTextWriterWriteAttribute (w, _xml ("style:class"),
165 xmlTextWriterEndElement (w); /* style:style */
169 xmlTextWriterStartElement (w, _xml ("style:style"));
170 xmlTextWriterWriteAttribute (w, _xml ("style:name"),
171 _xml ("Table_20_Contents"));
173 xmlTextWriterWriteAttribute (w, _xml ("style:display-name"),
174 _xml ("Table Contents"));
176 xmlTextWriterWriteAttribute (w, _xml ("style:family"),
179 xmlTextWriterWriteAttribute (w, _xml ("style:parent-style-name"),
182 xmlTextWriterWriteAttribute (w, _xml ("style:class"),
185 xmlTextWriterEndElement (w); /* style:style */
189 xmlTextWriterStartElement (w, _xml ("style:style"));
190 xmlTextWriterWriteAttribute (w, _xml ("style:name"),
191 _xml ("Table_20_Heading"));
193 xmlTextWriterWriteAttribute (w, _xml ("style:display-name"),
194 _xml ("Table Heading"));
196 xmlTextWriterWriteAttribute (w, _xml ("style:family"),
199 xmlTextWriterWriteAttribute (w, _xml ("style:parent-style-name"),
200 _xml ("Table_20_Contents"));
202 xmlTextWriterWriteAttribute (w, _xml ("style:class"),
206 xmlTextWriterStartElement (w, _xml ("style:text-properties"));
207 xmlTextWriterWriteAttribute (w, _xml ("fo:font-weight"), _xml ("bold"));
208 xmlTextWriterWriteAttribute (w, _xml ("style:font-weight-asian"), _xml ("bold"));
209 xmlTextWriterWriteAttribute (w, _xml ("style:font-weight-complex"), _xml ("bold"));
210 xmlTextWriterEndElement (w); /* style:text-properties */
212 xmlTextWriterEndElement (w); /* style:style */
216 xmlTextWriterEndElement (w); /* office:styles */
217 xmlTextWriterEndElement (w); /* office:document-styles */
219 xmlTextWriterEndDocument (w);
220 xmlFreeTextWriter (w);
221 zip_writer_add (odt->zip, file, "styles.xml");
222 close_temp_file (file);
226 write_meta_data (struct odt_driver *odt)
231 create_writer (&file, &w);
232 register_file (odt, "meta.xml");
234 xmlTextWriterStartElement (w, _xml ("office:document-meta"));
235 xmlTextWriterWriteAttribute (w, _xml ("xmlns:office"), _xml ("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
236 xmlTextWriterWriteAttribute (w, _xml ("xmlns:dc"), _xml ("http://purl.org/dc/elements/1.1/"));
237 xmlTextWriterWriteAttribute (w, _xml ("xmlns:meta"), _xml ("urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
238 xmlTextWriterWriteAttribute (w, _xml ("xmlns:ooo"), _xml("http://openoffice.org/2004/office"));
239 xmlTextWriterWriteAttribute (w, _xml ("office:version"), _xml("1.1"));
241 xmlTextWriterStartElement (w, _xml ("office:meta"));
243 xmlTextWriterStartElement (w, _xml ("meta:generator"));
244 xmlTextWriterWriteString (w, _xml (stat_version));
245 xmlTextWriterEndElement (w);
251 time_t t = time (NULL);
252 struct tm *tm = localtime (&t);
254 strftime (buf, 30, "%Y-%m-%dT%H:%M:%S", tm);
256 xmlTextWriterStartElement (w, _xml ("meta:creation-date"));
257 xmlTextWriterWriteString (w, _xml (buf));
258 xmlTextWriterEndElement (w);
260 xmlTextWriterStartElement (w, _xml ("dc:date"));
261 xmlTextWriterWriteString (w, _xml (buf));
262 xmlTextWriterEndElement (w);
267 struct passwd *pw = getpwuid (getuid ());
270 xmlTextWriterStartElement (w, _xml ("meta:initial-creator"));
271 xmlTextWriterWriteString (w, _xml (strtok (pw->pw_gecos, ",")));
272 xmlTextWriterEndElement (w);
274 xmlTextWriterStartElement (w, _xml ("dc:creator"));
275 xmlTextWriterWriteString (w, _xml (strtok (pw->pw_gecos, ",")));
276 xmlTextWriterEndElement (w);
281 xmlTextWriterEndElement (w);
282 xmlTextWriterEndElement (w);
283 xmlTextWriterEndDocument (w);
284 xmlFreeTextWriter (w);
285 zip_writer_add (odt->zip, file, "meta.xml");
286 close_temp_file (file);
289 static struct output_driver *
290 odt_create (const char *file_name, enum settings_output_devices device_type,
291 struct string_map *o UNUSED)
293 struct output_driver *d;
294 struct odt_driver *odt;
295 struct zip_writer *zip;
297 zip = zip_writer_create (file_name);
301 odt = xzalloc (sizeof *odt);
303 output_driver_init (d, &odt_driver_class, file_name, device_type);
306 odt->file_name = xstrdup (file_name);
308 if (!create_mimetype (zip))
310 output_driver_destroy (d);
314 /* Create the manifest */
315 create_writer (&odt->manifest_file, &odt->manifest_wtr);
317 xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:manifest"));
318 xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("xmlns:manifest"),
319 _xml("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"));
322 /* Add a manifest entry for the document as a whole */
323 xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:file-entry"));
324 xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:media-type"), _xml("application/vnd.oasis.opendocument.text"));
325 xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("manifest:full-path"), _xml("/"));
326 xmlTextWriterEndElement (odt->manifest_wtr);
329 write_meta_data (odt);
330 write_style_data (odt);
332 create_writer (&odt->content_file, &odt->content_wtr);
333 register_file (odt, "content.xml");
336 /* Some necessary junk at the start */
337 xmlTextWriterStartElement (odt->content_wtr, _xml("office:document-content"));
338 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:office"),
339 _xml("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
341 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:text"),
342 _xml("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
344 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("xmlns:table"),
345 _xml("urn:oasis:names:tc:opendocument:xmlns:table:1.0"));
347 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("office:version"), _xml("1.1"));
349 xmlTextWriterStartElement (odt->content_wtr, _xml("office:body"));
350 xmlTextWriterStartElement (odt->content_wtr, _xml("office:text"));
354 /* Close the manifest */
355 xmlTextWriterEndElement (odt->manifest_wtr);
356 xmlTextWriterEndDocument (odt->manifest_wtr);
357 xmlFreeTextWriter (odt->manifest_wtr);
358 zip_writer_add (odt->zip, odt->manifest_file, "META-INF/manifest.xml");
359 close_temp_file (odt->manifest_file);
365 odt_destroy (struct output_driver *driver)
367 struct odt_driver *odt = odt_driver_cast (driver);
369 if (odt->content_wtr != NULL)
371 xmlTextWriterEndElement (odt->content_wtr); /* office:text */
372 xmlTextWriterEndElement (odt->content_wtr); /* office:body */
373 xmlTextWriterEndElement (odt->content_wtr); /* office:document-content */
375 xmlTextWriterEndDocument (odt->content_wtr);
376 xmlFreeTextWriter (odt->content_wtr);
377 zip_writer_add (odt->zip, odt->content_file, "content.xml");
378 close_temp_file (odt->content_file);
380 zip_writer_close (odt->zip);
383 free (odt->file_name);
384 free (odt->command_name);
389 odt_submit_table (struct odt_driver *odt, struct table_item *item)
391 const struct table *tab = table_item_get_table (item);
392 const char *caption = table_item_get_caption (item);
395 /* Write a heading for the table */
398 xmlTextWriterStartElement (odt->content_wtr, _xml("text:h"));
399 xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("text:level"),
401 xmlTextWriterWriteString (odt->content_wtr,
402 _xml (table_item_get_caption (item)) );
403 xmlTextWriterEndElement (odt->content_wtr);
407 xmlTextWriterStartElement (odt->content_wtr, _xml("table:table"));
408 xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:name"),
409 "TABLE-%d", odt->table_num++);
412 /* Start column definitions */
413 xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-column"));
414 xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:number-columns-repeated"), "%d", table_nc (tab));
415 xmlTextWriterEndElement (odt->content_wtr);
418 /* Deal with row headers */
419 if ( table_ht (tab) > 0)
420 xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-header-rows"));
423 /* Write all the rows */
424 for (r = 0 ; r < table_nr (tab); ++r)
426 /* Start row definition */
427 xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-row"));
429 /* Write all the columns */
430 for (c = 0 ; c < table_nc (tab) ; ++c)
432 struct table_cell cell;
434 table_get_cell (tab, c, r, &cell);
436 if (c == cell.d[TABLE_HORZ][0] && r == cell.d[TABLE_VERT][0])
438 int colspan = table_cell_colspan (&cell);
439 int rowspan = table_cell_rowspan (&cell);
441 xmlTextWriterStartElement (odt->content_wtr, _xml("table:table-cell"));
442 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("office:value-type"), _xml("string"));
445 xmlTextWriterWriteFormatAttribute (
446 odt->content_wtr, _xml("table:number-columns-spanned"),
450 xmlTextWriterWriteFormatAttribute (
451 odt->content_wtr, _xml("table:number-rows-spanned"),
454 xmlTextWriterStartElement (odt->content_wtr, _xml("text:p"));
456 if ( r < table_ht (tab) || c < table_hl (tab) )
457 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Heading"));
459 xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Contents"));
461 xmlTextWriterWriteString (odt->content_wtr, _xml(cell.contents));
463 xmlTextWriterEndElement (odt->content_wtr); /* text:p */
464 xmlTextWriterEndElement (odt->content_wtr); /* table:table-cell */
468 xmlTextWriterStartElement (odt->content_wtr, _xml("table:covered-table-cell"));
469 xmlTextWriterEndElement (odt->content_wtr);
472 table_cell_free (&cell);
475 xmlTextWriterEndElement (odt->content_wtr); /* row */
477 if ( table_ht (tab) > 0 && r == table_ht (tab) - 1)
478 xmlTextWriterEndElement (odt->content_wtr); /* table-header-rows */
481 xmlTextWriterEndElement (odt->content_wtr); /* table */
485 odt_output_text (struct odt_driver *odt, const char *text)
487 xmlTextWriterStartElement (odt->content_wtr, _xml("text:p"));
488 xmlTextWriterWriteString (odt->content_wtr, _xml(text));
489 xmlTextWriterEndElement (odt->content_wtr);
492 /* Submit a table to the ODT driver */
494 odt_submit (struct output_driver *driver,
495 const struct output_item *output_item)
497 struct odt_driver *odt = odt_driver_cast (driver);
499 output_driver_track_current_command (output_item, &odt->command_name);
501 if (is_table_item (output_item))
502 odt_submit_table (odt, to_table_item (output_item));
503 else if (is_text_item (output_item))
505 struct text_item *text_item = to_text_item (output_item);
507 if (text_item_get_type (text_item) != TEXT_ITEM_COMMAND_CLOSE)
508 odt_output_text (odt, text_item_get_text (text_item));
510 else if (is_message_item (output_item))
512 const struct message_item *message_item = to_message_item (output_item);
513 const struct msg *msg = message_item_get_msg (message_item);
514 char *s = msg_to_string (msg, odt->command_name);
515 odt_output_text (odt, s);
520 struct output_driver_factory odt_driver_factory =
521 { "odt", "pspp.odf", odt_create };
523 static const struct output_driver_class odt_driver_class =