1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * xls-write-docprops.c: MS Excel XLSX export of document properties
5 * Copyright (C) 2011 Andreas J. Guelzow, All rights reserved
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
26 * DO * NOT * COMPILE * DIRECTLY *
27 * DO * NOT * COMPILE * DIRECTLY *
28 * DO * NOT * COMPILE * DIRECTLY *
30 * included via xlsx-write.c
33 typedef void (*output_function
) (GsfXMLOut
*output
, GValue
const *val
);
36 xlsx_map_time_to_int (GsfXMLOut
*output
, GValue
const *val
)
38 switch (G_VALUE_TYPE(val
)) {
40 gsf_xml_out_add_gvalue (output
, NULL
, val
);
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
)) {
48 gsf_xml_out_add_int (output
, NULL
, minutes
);
54 gsf_xml_out_add_int (output
, NULL
, 0);
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
);
65 gsf_xml_out_add_int (output
, NULL
, 0);
69 xlsx_map_to_bool (GsfXMLOut
*output
, GValue
const *val
)
71 switch (G_VALUE_TYPE(val
)) {
73 xlsx_add_bool (output
, NULL
, g_value_get_boolean (val
));
76 xlsx_add_bool (output
, NULL
, g_value_get_int (val
) != 0);
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"));
84 xlsx_add_bool (output
, NULL
, FALSE
);
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 ();
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
);
102 gsf_timestamp_free (ts
);
104 GsfTimestamp
* ts
= gsf_timestamp_new ();
107 g_get_current_time (&tm
);
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
);
113 gsf_timestamp_free (ts
);
118 xlsx_map_to_keys (GsfXMLOut
*output
, GValue
const *val
)
123 if (G_TYPE_STRING
== G_VALUE_TYPE (val
)) {
124 char const *str
= g_value_get_string (val
);
126 gsf_xml_out_add_cstr (output
, NULL
, str
);
127 } else if (NULL
!= (va
= gsf_value_get_docprop_varray (val
))) {
129 for (i
= 0 ; i
< va
->n_values
; i
++) {
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 */
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
)
152 output_function xlsx_output_fun
;
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
);
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
);
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
)
198 char const *xlsx_key
;
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
);
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
);
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
);
268 output_function of
= xlsx_map_prop_name_to_output_fun
273 gsf_xml_out_add_gvalue (output
, NULL
, val
);
275 gsf_xml_out_end_element (output
);
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",
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
));
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
);
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
);
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
)
320 char const *xlsx_key
;
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
);
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
);
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
);
361 output_function of
= xlsx_map_prop_name_to_output_fun
366 gsf_xml_out_add_gvalue (output
, NULL
, val
);
368 gsf_xml_out_end_element (output
);
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",
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
);
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
)
411 char const *name_key
;
417 int i
= G_N_ELEMENTS (map
);
419 xlsx_pid_map
= g_hash_table_new (g_str_hash
, g_str_equal
);
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
));
430 xlsx_meta_write_props_custom_type (char const *prop_name
, GValue
const *val
, GsfXMLOut
*xml
, char const *type
,
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}");
439 gsf_xml_out_add_int (xml
, "pid", pid
);
441 gsf_xml_out_add_int (xml
, "pid", *custom_pid
);
444 gsf_xml_out_add_cstr (xml
, "name", prop_name
);
445 gsf_xml_out_start_element (xml
, type
);
447 switch (G_VALUE_TYPE (val
)) {
449 gsf_xml_out_add_cstr (xml
, NULL
,
450 g_value_get_boolean (val
) ? "true" : "false");
453 gsf_xml_out_add_gvalue (xml
, NULL
, val
);
457 gsf_xml_out_end_element (xml
);
458 gsf_xml_out_end_element (xml
); /* </property> */
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
)) {
474 xlsx_meta_write_props_custom_type (prop_name
, val
, output
, "vt:bool", &state
->custom_prop_id
);
478 xlsx_meta_write_props_custom_type (prop_name
, val
, output
, "vt:i4", &state
->custom_prop_id
);
482 xlsx_meta_write_props_custom_type (prop_name
, val
, output
, "vt:decimal", &state
->custom_prop_id
);
485 xlsx_meta_write_props_custom_type (prop_name
, val
, output
, "vt:lpwstr", &state
->custom_prop_id
);
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",
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
);
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
);