2 * xls-write-docprops.c: MS Excel XLSX export of document properties
4 * Copyright (C) 2011 Andreas J. Guelzow, All rights reserved
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
25 * DO * NOT * COMPILE * DIRECTLY *
26 * DO * NOT * COMPILE * DIRECTLY *
27 * DO * NOT * COMPILE * DIRECTLY *
29 * included via xlsx-write.c
32 typedef void (*output_function
) (GsfXMLOut
*output
, GValue
const *val
);
35 xlsx_map_time_to_int (GsfXMLOut
*output
, GValue
const *val
)
37 switch (G_VALUE_TYPE(val
)) {
39 gsf_xml_out_add_gvalue (output
, NULL
, val
);
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
)) {
47 gsf_xml_out_add_int (output
, NULL
, minutes
);
53 gsf_xml_out_add_int (output
, NULL
, 0);
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
);
64 gsf_xml_out_add_int (output
, NULL
, 0);
68 xlsx_map_to_bool (GsfXMLOut
*output
, GValue
const *val
)
70 switch (G_VALUE_TYPE(val
)) {
72 xlsx_add_bool (output
, NULL
, g_value_get_boolean (val
));
75 xlsx_add_bool (output
, NULL
, g_value_get_int (val
) != 0);
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"));
83 xlsx_add_bool (output
, NULL
, FALSE
);
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 ();
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
);
101 gsf_timestamp_free (ts
);
103 GsfTimestamp
* ts
= gsf_timestamp_new ();
106 g_get_current_time (&tm
);
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
);
112 gsf_timestamp_free (ts
);
117 xlsx_map_to_keys (GsfXMLOut
*output
, GValue
const *val
)
122 if (G_TYPE_STRING
== G_VALUE_TYPE (val
)) {
123 char const *str
= g_value_get_string (val
);
125 gsf_xml_out_add_cstr (output
, NULL
, str
);
126 } else if (NULL
!= (va
= gsf_value_get_docprop_varray (val
))) {
128 for (i
= 0 ; i
< va
->n_values
; i
++) {
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 */
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
)
151 output_function xlsx_output_fun
;
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
);
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
);
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
)
197 char const *xlsx_key
;
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
);
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
);
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
);
267 output_function of
= xlsx_map_prop_name_to_output_fun
272 gsf_xml_out_add_gvalue (output
, NULL
, val
);
274 gsf_xml_out_end_element (output
);
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",
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
));
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
);
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
);
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
)
319 char const *xlsx_key
;
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
);
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
);
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
);
360 output_function of
= xlsx_map_prop_name_to_output_fun
365 gsf_xml_out_add_gvalue (output
, NULL
, val
);
367 gsf_xml_out_end_element (output
);
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",
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
);
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
)
410 char const *name_key
;
416 int i
= G_N_ELEMENTS (map
);
418 xlsx_pid_map
= g_hash_table_new (g_str_hash
, g_str_equal
);
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
));
429 xlsx_meta_write_props_custom_type (char const *prop_name
, GValue
const *val
, GsfXMLOut
*xml
, char const *type
,
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}");
438 gsf_xml_out_add_int (xml
, "pid", pid
);
440 gsf_xml_out_add_int (xml
, "pid", *custom_pid
);
443 gsf_xml_out_add_cstr (xml
, "name", prop_name
);
444 gsf_xml_out_start_element (xml
, type
);
446 switch (G_VALUE_TYPE (val
)) {
448 gsf_xml_out_add_cstr (xml
, NULL
,
449 g_value_get_boolean (val
) ? "true" : "false");
452 gsf_xml_out_add_gvalue (xml
, NULL
, val
);
456 gsf_xml_out_end_element (xml
);
457 gsf_xml_out_end_element (xml
); /* </property> */
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
)) {
473 xlsx_meta_write_props_custom_type (prop_name
, val
, output
, "vt:bool", &state
->custom_prop_id
);
477 xlsx_meta_write_props_custom_type (prop_name
, val
, output
, "vt:i4", &state
->custom_prop_id
);
481 xlsx_meta_write_props_custom_type (prop_name
, val
, output
, "vt:decimal", &state
->custom_prop_id
);
484 xlsx_meta_write_props_custom_type (prop_name
, val
, output
, "vt:lpwstr", &state
->custom_prop_id
);
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",
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
);
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
);