Compilation: prefer glib functions over goffice equivalents
[gnumeric.git] / plugins / excel / xlsx-write-docprops.c
blob9f2acd464d3b6b9bcf81130c02fc762cc4346cd4
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /**
3 * xls-write-docprops.c: MS Excel XLSX export of document properties
5 * Copyright (C) 2011 Andreas J. Guelzow, All rights reserved
6 * aguelzow@pyrshep.ca
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
22 **/
26 * DO * NOT * COMPILE * DIRECTLY *
27 * DO * NOT * COMPILE * DIRECTLY *
28 * DO * NOT * COMPILE * DIRECTLY *
30 * included via xlsx-write.c
31 **/
33 typedef void (*output_function) (GsfXMLOut *output, GValue const *val);
35 static void
36 xlsx_map_time_to_int (GsfXMLOut *output, GValue const *val)
38 switch (G_VALUE_TYPE(val)) {
39 case G_TYPE_INT:
40 gsf_xml_out_add_gvalue (output, NULL, val);
41 break;
42 case G_TYPE_STRING: {
43 char const *str = g_value_get_string (val);
44 int minutes = 0, seconds = 0;
45 if ( 1 < sscanf (str, "PT%dM%dS", &minutes, &seconds)) {
46 if (seconds > 29)
47 minutes++;
48 gsf_xml_out_add_int (output, NULL, minutes);
49 break;
51 /* no break */
53 default:
54 gsf_xml_out_add_int (output, NULL, 0);
55 break;
59 static void
60 xlsx_map_to_int (GsfXMLOut *output, GValue const *val)
62 if (G_TYPE_INT == G_VALUE_TYPE (val))
63 gsf_xml_out_add_gvalue (output, NULL, val);
64 else
65 gsf_xml_out_add_int (output, NULL, 0);
68 static void
69 xlsx_map_to_bool (GsfXMLOut *output, GValue const *val)
71 switch (G_VALUE_TYPE(val)) {
72 case G_TYPE_BOOLEAN:
73 xlsx_add_bool (output, NULL, g_value_get_boolean (val));
74 break;
75 case G_TYPE_INT:
76 xlsx_add_bool (output, NULL, g_value_get_int (val) != 0);
77 break;
78 case G_TYPE_STRING:
79 xlsx_add_bool (output, NULL,
80 0 == g_ascii_strcasecmp (g_value_get_string (val), "true")
81 || 0 == g_ascii_strcasecmp (g_value_get_string (val), "yes"));
82 break;
83 default:
84 xlsx_add_bool (output, NULL, FALSE);
85 break;
89 static void
90 xlsx_map_to_date_core (GsfXMLOut *output, GValue const *val)
92 gsf_xml_out_add_cstr_unchecked (output, "xsi:type", "dcterms:W3CDTF");
93 if (VAL_IS_GSF_TIMESTAMP(val))
94 gsf_xml_out_add_gvalue (output, NULL, val);
95 else if (G_TYPE_INT == G_VALUE_TYPE (val)) {
96 GsfTimestamp * ts = gsf_timestamp_new ();
97 char *str;
98 gsf_timestamp_set_time (ts, g_value_get_int (val));
99 str = gsf_timestamp_as_string (ts);
100 gsf_xml_out_add_cstr (output, NULL, str);
101 g_free (str);
102 gsf_timestamp_free (ts);
103 } else {
104 GsfTimestamp * ts = gsf_timestamp_new ();
105 char *str;
106 GTimeVal tm;
107 g_get_current_time (&tm);
108 tm.tv_usec = 0L;
109 gsf_timestamp_set_time (ts, tm.tv_sec);
110 str = gsf_timestamp_as_string (ts);
111 gsf_xml_out_add_cstr (output, NULL, str);
112 g_free (str);
113 gsf_timestamp_free (ts);
117 static void
118 xlsx_map_to_keys (GsfXMLOut *output, GValue const *val)
120 GValueArray *va;
121 unsigned i;
123 if (G_TYPE_STRING == G_VALUE_TYPE (val)) {
124 char const *str = g_value_get_string (val);
125 if (str && *str)
126 gsf_xml_out_add_cstr (output, NULL, str);
127 } else if (NULL != (va = gsf_value_get_docprop_varray (val))) {
128 char *str;
129 for (i = 0 ; i < va->n_values; i++) {
130 if (i!=0)
131 gsf_xml_out_add_cstr_unchecked (output, NULL, " ");
132 str = g_value_dup_string (g_value_array_get_nth (va, i));
133 g_strdelimit (str," \t\n\r",'_');
134 gsf_xml_out_add_cstr (output, NULL, str);
135 /* In Edition 2 we would be allowed to have different */
136 /* sets of keywords depending on laguage */
137 g_free (str);
142 static output_function
143 xlsx_map_prop_name_to_output_fun (char const *name)
145 /* shared by all instances and never freed */
146 static GHashTable *xlsx_prop_name_map_output_fun_extended = NULL;
148 if (NULL == xlsx_prop_name_map_output_fun_extended)
150 static struct {
151 char const *gsf_key;
152 output_function xlsx_output_fun;
153 } const map [] = {
154 { GSF_META_NAME_DATE_CREATED, xlsx_map_to_date_core},
155 { GSF_META_NAME_DATE_MODIFIED, xlsx_map_to_date_core},
156 { GSF_META_NAME_EDITING_DURATION, xlsx_map_time_to_int},
157 { GSF_META_NAME_KEYWORDS, xlsx_map_to_keys},
158 { GSF_META_NAME_CHARACTER_COUNT, xlsx_map_to_int},
159 { GSF_META_NAME_BYTE_COUNT, xlsx_map_to_int},
160 { GSF_META_NAME_SECURITY, xlsx_map_to_int},
161 { GSF_META_NAME_HIDDEN_SLIDE_COUNT, xlsx_map_to_int},
162 { "xlsx:HyperlinksChanged", xlsx_map_to_bool},
163 { GSF_META_NAME_LINE_COUNT, xlsx_map_to_int},
164 { GSF_META_NAME_LINKS_DIRTY, xlsx_map_to_bool},
165 { GSF_META_NAME_MM_CLIP_COUNT, xlsx_map_to_int},
166 { GSF_META_NAME_NOTE_COUNT, xlsx_map_to_int},
167 { GSF_META_NAME_PAGE_COUNT, xlsx_map_to_int},
168 { GSF_META_NAME_PARAGRAPH_COUNT, xlsx_map_to_int},
169 { "xlsx:SharedDoc", xlsx_map_to_bool},
170 { GSF_META_NAME_SCALE, xlsx_map_to_bool},
171 { GSF_META_NAME_SLIDE_COUNT, xlsx_map_to_int},
172 { GSF_META_NAME_WORD_COUNT, xlsx_map_to_int}
175 int i = G_N_ELEMENTS (map);
177 xlsx_prop_name_map_output_fun_extended = g_hash_table_new (g_str_hash, g_str_equal);
178 while (i-- > 0)
179 g_hash_table_insert (xlsx_prop_name_map_output_fun_extended,
180 (gpointer)map[i].gsf_key,
181 (gpointer)map[i].xlsx_output_fun);
184 return g_hash_table_lookup (xlsx_prop_name_map_output_fun_extended, name);
188 static char const *
189 xlsx_map_prop_name_extended (char const *name)
191 /* shared by all instances and never freed */
192 static GHashTable *xlsx_prop_name_map_extended = NULL;
194 if (NULL == xlsx_prop_name_map_extended)
196 static struct {
197 char const *gsf_key;
198 char const *xlsx_key;
199 } const map [] = {
200 { GSF_META_NAME_TEMPLATE, "Template"},
201 { GSF_META_NAME_MANAGER, "Manager"},
202 { GSF_META_NAME_COMPANY, "Company"},
203 { GSF_META_NAME_PAGE_COUNT, "Pages"},
204 { GSF_META_NAME_WORD_COUNT, "Words"},
205 { GSF_META_NAME_CHARACTER_COUNT, "Characters"},
206 { GSF_META_NAME_PRESENTATION_FORMAT, "PresentationFormat"},
207 { GSF_META_NAME_LINE_COUNT, "Lines"},
208 { GSF_META_NAME_PARAGRAPH_COUNT, "Paragraphs"},
209 { GSF_META_NAME_SLIDE_COUNT, "Slides"},
210 { GSF_META_NAME_NOTE_COUNT, "Notes"},
211 { GSF_META_NAME_EDITING_DURATION, "TotalTime"},
212 { GSF_META_NAME_HIDDEN_SLIDE_COUNT, "HiddenSlides"},
213 { "xlsx:MMClips", "MMClips"},
214 { GSF_META_NAME_SCALE, "ScaleCrop"},
215 /* { GSF_META_NAME_HEADING_PAIRS, "HeadingPairs"}, */
216 /* type="CT_VectorVariant" */
217 /* { , "TitlesOfParts"}, type="CT_VectorLpstr"> */
218 { GSF_META_NAME_LINKS_DIRTY, "LinksUpToDate"},
219 { GSF_META_NAME_BYTE_COUNT, "CharactersWithSpaces"},
220 { "xlsx:SharedDoc", "SharedDoc"},
221 { "xlsx:HyperlinkBase", "HyperlinkBase"},
222 /* { , "HLinks"}, type="CT_VectorVariant" */
223 { "xlsx:HyperlinksChanged", "HyperlinksChanged"},
224 /* { , "DigSig"}, type="CT_DigSigBlob" */
225 { GSF_META_NAME_SECURITY, "DocSecurity"}
228 /* Not matching ECMA-376 edition 1 core or extended properties: */
229 /* GSF_META_NAME_CODEPAGE */
230 /* GSF_META_NAME_CASE_SENSITIVE */
231 /* GSF_META_NAME_CELL_COUNT */
232 /* GSF_META_NAME_DICTIONARY */
233 /* GSF_META_NAME_DOCUMENT_PARTS */
234 /* GSF_META_NAME_IMAGE_COUNT */
235 /* GSF_META_NAME_LAST_SAVED_BY */
236 /* GSF_META_NAME_LOCALE_SYSTEM_DEFAULT */
237 /* GSF_META_NAME_THUMBNAIL */
238 /* GSF_META_NAME_MM_CLIP_COUNT */
239 /* GSF_META_NAME_OBJECT_COUNT */
240 /* GSF_META_NAME_SPREADSHEET_COUNT */
241 /* GSF_META_NAME_TABLE_COUNT */
242 /* GSF_META_NAME_GENERATOR stored as Application and AppVersion */
243 /* GSF_META_NAME_KEYWORD cmp with GSF_META_NAME_KEYWORDS in core*/
244 /* GSF_META_NAME_LAST_PRINTED cmp with GSF_META_NAME_PRINT_DATE in core*/
245 /* GSF_META_NAME_PRINTED_BY */
247 int i = G_N_ELEMENTS (map);
249 xlsx_prop_name_map_extended = g_hash_table_new (g_str_hash, g_str_equal);
250 while (i-- > 0)
251 g_hash_table_insert (xlsx_prop_name_map_extended,
252 (gpointer)map[i].gsf_key,
253 (gpointer)map[i].xlsx_key);
256 return g_hash_table_lookup (xlsx_prop_name_map_extended, name);
259 static void
260 xlsx_meta_write_props_extended (char const *prop_name, GsfDocProp *prop, GsfXMLOut *output)
262 char const *mapped_name;
263 GValue const *val = gsf_doc_prop_get_val (prop);
265 if (NULL != (mapped_name = xlsx_map_prop_name_extended (prop_name))) {
266 gsf_xml_out_start_element (output, mapped_name);
267 if (NULL != val) {
268 output_function of = xlsx_map_prop_name_to_output_fun
269 (prop_name);
270 if (of != NULL)
271 of (output, val);
272 else
273 gsf_xml_out_add_gvalue (output, NULL, val);
275 gsf_xml_out_end_element (output);
279 static void
280 xlsx_write_docprops_app (XLSXWriteState *state, GsfOutfile *root_part, GsfOutfile *docprops_dir)
282 GsfOutput *part = gsf_outfile_open_pkg_add_rel
283 (docprops_dir, "app.xml",
284 "application/vnd.openxmlformats-officedocument.extended-properties+xml",
285 root_part,
286 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties");
287 GsfXMLOut *xml = gsf_xml_out_new (part);
288 GsfDocMetaData *meta = go_doc_get_meta_data (GO_DOC (state->base.wb));
289 char *version;
291 gsf_xml_out_start_element (xml, "Properties");
292 gsf_xml_out_add_cstr_unchecked (xml, "xmlns", ns_docprops_extended);
293 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:vt", ns_docprops_extended_vt);
294 gsf_xml_out_simple_element (xml, "Application", PACKAGE_NAME);
296 /*1.10.17 is not permitted for AppVersion, so we need to convert it to 1.1017 */
297 version = g_strdup_printf ("%d.%02d%02d", GNM_VERSION_EPOCH, GNM_VERSION_MAJOR, GNM_VERSION_MINOR);
298 gsf_xml_out_simple_element (xml, "AppVersion", version);
299 g_free (version);
301 gsf_doc_meta_data_foreach (meta, (GHFunc) xlsx_meta_write_props_extended, xml);
303 gsf_xml_out_end_element (xml); /* </Properties> */
305 g_object_unref (xml);
306 gsf_output_close (part);
307 g_object_unref (part);
310 static char const *
311 xlsx_map_prop_name (char const *name)
313 /* shared by all instances and never freed */
314 static GHashTable *xlsx_prop_name_map = NULL;
316 if (NULL == xlsx_prop_name_map)
318 static struct {
319 char const *gsf_key;
320 char const *xlsx_key;
321 } const map [] = {
322 { GSF_META_NAME_CATEGORY, "cp:category" },
323 { "cp:contentStatus", "cp:contentStatus" },
324 { "cp:contentType", "cp:contentType" },
325 { GSF_META_NAME_KEYWORDS, "cp:keywords" },
326 { GSF_META_NAME_CREATOR, "cp:lastModifiedBy" },
327 { GSF_META_NAME_PRINT_DATE, "cp:lastPrinted" },
328 { GSF_META_NAME_REVISION_COUNT, "cp:revision" },
329 { "cp:version", "cp:version" },
330 { GSF_META_NAME_INITIAL_CREATOR,"dc:creator" },
331 { GSF_META_NAME_DESCRIPTION, "dc:description" },
332 { "dc:identifier", "dc:identifier" },
333 { GSF_META_NAME_LANGUAGE, "dc:language" },
334 { GSF_META_NAME_SUBJECT, "dc:subject" },
335 { GSF_META_NAME_TITLE, "dc:title" },
336 { GSF_META_NAME_DATE_CREATED, "dcterms:created" },
337 { GSF_META_NAME_DATE_MODIFIED, "dcterms:modified" }
340 int i = G_N_ELEMENTS (map);
342 xlsx_prop_name_map = g_hash_table_new (g_str_hash, g_str_equal);
343 while (i-- > 0)
344 g_hash_table_insert (xlsx_prop_name_map,
345 (gpointer)map[i].gsf_key,
346 (gpointer)map[i].xlsx_key);
349 return g_hash_table_lookup (xlsx_prop_name_map, name);
352 static void
353 xlsx_meta_write_props (char const *prop_name, GsfDocProp *prop, GsfXMLOut *output)
355 char const *mapped_name;
356 GValue const *val = gsf_doc_prop_get_val (prop);
358 if (NULL != (mapped_name = xlsx_map_prop_name (prop_name))) {
359 gsf_xml_out_start_element (output, mapped_name);
360 if (NULL != val) {
361 output_function of = xlsx_map_prop_name_to_output_fun
362 (prop_name);
363 if (of != NULL)
364 of (output, val);
365 else
366 gsf_xml_out_add_gvalue (output, NULL, val);
368 gsf_xml_out_end_element (output);
373 static void
374 xlsx_write_docprops_core (XLSXWriteState *state, GsfOutfile *root_part, GsfOutfile *docprops_dir)
376 GsfOutput *part = gsf_outfile_open_pkg_add_rel
377 (docprops_dir, "core.xml",
378 "application/vnd.openxmlformats-package.core-properties+xml",
379 root_part,
380 /* According to 15.2.12.1 this ought to be "http://schemas.openxmlformats.org/officedocument/2006/relationships/metadata/core-properties" */
381 /* but this is what MS Office apparently writes. As a side effect this makes us fail strict validation */
382 "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties");
383 GsfXMLOut *xml = gsf_xml_out_new (part);
384 GsfDocMetaData *meta = go_doc_get_meta_data (GO_DOC (state->base.wb));
386 gsf_xml_out_start_element (xml, "cp:coreProperties");
387 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:cp", ns_docprops_core_cp);
388 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:dc", ns_docprops_core_dc);
389 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:dcmitype", ns_docprops_core_dcmitype);
390 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:dcterms", ns_docprops_core_dcterms);
391 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:xsi", ns_docprops_core_xsi);
393 gsf_doc_meta_data_foreach (meta, (GHFunc) xlsx_meta_write_props, xml);
395 gsf_xml_out_end_element (xml); /* </cp:coreProperties> */
397 g_object_unref (xml);
398 gsf_output_close (part);
399 g_object_unref (part);
402 static int
403 xlsx_map_to_pid (char const *name)
405 /* shared by all instances and never freed */
406 static GHashTable *xlsx_pid_map = NULL;
408 if (NULL == xlsx_pid_map)
410 static struct {
411 char const *name_key;
412 int pid_key;
413 } const map [] = {
414 { "Editor", 2}
417 int i = G_N_ELEMENTS (map);
419 xlsx_pid_map = g_hash_table_new (g_str_hash, g_str_equal);
420 while (i-- > 0)
421 g_hash_table_insert (xlsx_pid_map,
422 (gpointer)map[i].name_key,
423 GINT_TO_POINTER (map[i].pid_key));
426 return GPOINTER_TO_INT (g_hash_table_lookup (xlsx_pid_map, name));
429 static void
430 xlsx_meta_write_props_custom_type (char const *prop_name, GValue const *val, GsfXMLOut *xml, char const *type,
431 int *custom_pid)
433 int pid = xlsx_map_to_pid (prop_name);
436 gsf_xml_out_start_element (xml, "property");
437 gsf_xml_out_add_cstr_unchecked (xml, "fmtid", "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}");
438 if (pid != 0)
439 gsf_xml_out_add_int (xml, "pid", pid);
440 else {
441 gsf_xml_out_add_int (xml, "pid", *custom_pid);
442 *custom_pid += 1;
444 gsf_xml_out_add_cstr (xml, "name", prop_name);
445 gsf_xml_out_start_element (xml, type);
446 if (NULL != val) {
447 switch (G_VALUE_TYPE (val)) {
448 case G_TYPE_BOOLEAN:
449 gsf_xml_out_add_cstr (xml, NULL,
450 g_value_get_boolean (val) ? "true" : "false");
451 break;
452 default:
453 gsf_xml_out_add_gvalue (xml, NULL, val);
454 break;
457 gsf_xml_out_end_element (xml);
458 gsf_xml_out_end_element (xml); /* </property> */
461 static void
462 xlsx_meta_write_props_custom (char const *prop_name, GsfDocProp *prop, XLSXClosure *info)
464 GsfXMLOut *output = info->xml;
465 XLSXWriteState *state = info->state;
467 if ((0 != strcmp (GSF_META_NAME_GENERATOR, prop_name)) && (NULL == xlsx_map_prop_name (prop_name))
468 && (NULL == xlsx_map_prop_name_extended (prop_name))) {
469 GValue const *val = gsf_doc_prop_get_val (prop);
470 if (VAL_IS_GSF_TIMESTAMP(val))
471 xlsx_meta_write_props_custom_type (prop_name, val, output, "vt:date", &state->custom_prop_id);
472 else switch (G_VALUE_TYPE(val)) {
473 case G_TYPE_BOOLEAN:
474 xlsx_meta_write_props_custom_type (prop_name, val, output, "vt:bool", &state->custom_prop_id);
475 break;
476 case G_TYPE_INT:
477 case G_TYPE_LONG:
478 xlsx_meta_write_props_custom_type (prop_name, val, output, "vt:i4", &state->custom_prop_id);
479 break;
480 case G_TYPE_FLOAT:
481 case G_TYPE_DOUBLE:
482 xlsx_meta_write_props_custom_type (prop_name, val, output, "vt:decimal", &state->custom_prop_id);
483 break;
484 case G_TYPE_STRING:
485 xlsx_meta_write_props_custom_type (prop_name, val, output, "vt:lpwstr", &state->custom_prop_id);
486 break;
487 default:
488 break;
493 static void
494 xlsx_write_docprops_custom (XLSXWriteState *state, GsfOutfile *root_part, GsfOutfile *docprops_dir)
496 GsfOutput *part = gsf_outfile_open_pkg_add_rel
497 (docprops_dir, "custom.xml",
498 "application/vnd.openxmlformats-officedocument.custom-properties+xml",
499 root_part,
500 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties");
501 GsfXMLOut *xml = gsf_xml_out_new (part);
502 GsfDocMetaData *meta = go_doc_get_meta_data (GO_DOC (state->base.wb));
503 XLSXClosure info = { state, xml };
505 gsf_xml_out_start_element (xml, "Properties");
506 gsf_xml_out_add_cstr_unchecked (xml, "xmlns", ns_docprops_custom);
507 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:vt", ns_docprops_extended_vt);
509 gsf_doc_meta_data_foreach (meta, (GHFunc) xlsx_meta_write_props_custom, &info);
511 gsf_xml_out_end_element (xml); /* </Properties> */
513 g_object_unref (xml);
514 gsf_output_close (part);
515 g_object_unref (part);
518 static void
519 xlsx_write_docprops (XLSXWriteState *state, GsfOutfile *root_part)
521 GsfOutfile *docprops_dir = (GsfOutfile *)gsf_outfile_new_child (root_part, "docProps", TRUE);
523 xlsx_write_docprops_app (state, root_part, docprops_dir);
524 xlsx_write_docprops_core (state, root_part, docprops_dir);
525 xlsx_write_docprops_custom (state, root_part, docprops_dir);
527 gsf_output_close (GSF_OUTPUT (docprops_dir));
528 g_object_unref (docprops_dir);