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