Code cleanup
[gnumeric.git] / plugins / excel / xlsx-write.c
blob0a38bdb2c442afe2fe1f1c53dac92959ce502dc4
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * xlsx-write.c : export MS Office Open xlsx files.
5 * Copyright (C) 2006-2007 Jody Goldberg (jody@gnome.org)
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) version 3.
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
23 /*****************************************************************************/
25 #include <gnumeric-config.h>
26 #include <goffice/goffice.h>
28 #include "ms-excel-write.h"
29 #include "xlsx-utils.h"
31 #include "parse-util.h"
32 #include "workbook.h"
33 #include "workbook-priv.h"
34 #include "workbook-view.h"
35 #include "sheet.h"
36 #include "sheet-style.h"
37 #include "sheet-view.h"
38 #include "sheet-filter.h"
39 #include "sheet-filter-combo.h"
40 #include "ranges.h"
41 #include "value.h"
42 #include "cell.h"
43 #include "expr.h"
44 #include "func.h"
45 #include "style-color.h"
46 #include "validation.h"
47 #include "hlink.h"
48 #include "input-msg.h"
49 #include "print-info.h"
50 #include "gutils.h"
51 #include "sheet-object.h"
52 #include "sheet-object-cell-comment.h"
53 #include "sheet-object-graph.h"
54 #include "sheet-object-widget.h"
55 #include "sheet-object-image.h"
56 #include "gnm-so-line.h"
57 #include "gnm-so-filled.h"
58 #include "graph.h"
59 #include "style-border.h"
60 #include "style-conditions.h"
61 #include "gutils.h"
62 #include "expr-name.h"
64 #include "go-val.h"
66 #include <gsf/gsf-output.h>
67 #include <gsf/gsf-outfile.h>
68 #include <gsf/gsf-outfile-zip.h>
69 #include <gsf/gsf-open-pkg-utils.h>
70 #include <gsf/gsf-utils.h>
71 #include <gsf/gsf-libxml.h>
72 #include <glib/gi18n-lib.h>
73 #include <gsf/gsf-meta-names.h>
74 #include <gsf/gsf-doc-meta-data.h>
75 #include <gsf/gsf-docprop-vector.h>
76 #include <gsf/gsf-timestamp.h>
77 #include <gmodule.h>
78 #include <string.h>
80 #define NUM_FORMAT_BASE 100
82 enum {
83 ECMA_376_2006 = 1,
84 ECMA_376_2008 = 2
87 static char const *ns_ss = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
88 static char const *ns_ss_drawing = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
89 static char const *ns_docprops_core_cp = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
90 static char const *ns_docprops_core_dc = "http://purl.org/dc/elements/1.1/";
91 static char const *ns_docprops_core_dcmitype = "http://purl.org/dc/dcmitype/";
92 static char const *ns_docprops_core_dcterms = "http://purl.org/dc/terms/";
93 static char const *ns_docprops_core_xsi = "http://www.w3.org/2001/XMLSchema-instance";
94 static char const *ns_docprops_extended = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties";
95 static char const *ns_docprops_extended_vt = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
96 static char const *ns_docprops_custom = "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties";
97 static char const *ns_drawing = "http://schemas.openxmlformats.org/drawingml/2006/main";
98 static char const *ns_chart = "http://schemas.openxmlformats.org/drawingml/2006/chart";
99 static char const *ns_vml = "urn:schemas-microsoft-com:vml";
100 static char const *ns_leg_office = "urn:schemas-microsoft-com:office:office";
101 static char const *ns_leg_excel = "urn:schemas-microsoft-com:office:excel";
103 static char const *ns_gnm_ext = "http://www.gnumeric.org/ext/spreadsheetml";
105 static char const *ns_rel = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
106 static char const *ns_rel_hlink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
107 static char const *ns_rel_draw = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
108 static char const *ns_rel_leg_draw = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing";
109 static char const *ns_rel_chart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
110 static char const *ns_rel_com = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
111 static char const *ns_rel_image = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
114 typedef struct {
115 unsigned int count;
116 GsfOutfile *parent;
117 GsfOutfile *dir;
118 const char *name;
119 } XLSXDir;
121 static void
122 xlsx_dir_init (XLSXDir *dir, GsfOutfile *parent, const char *name)
124 dir->count = 0;
125 dir->parent = parent;
126 dir->dir = NULL;
127 dir->name = name;
130 static void
131 xlsx_dir_close (XLSXDir *dir)
133 if (dir->dir) {
134 gsf_output_close (GSF_OUTPUT (dir->dir));
135 g_object_unref (dir->dir);
136 dir->dir = NULL;
140 static GsfOutfile *
141 xlsx_dir_get (XLSXDir *dir)
143 if (!dir->dir) {
144 char *debug_name = g_strdup_printf ("xlsx directory %s", dir->name);
145 dir->dir = (GsfOutfile *)gsf_outfile_new_child (dir->parent, dir->name, TRUE);
146 go_debug_check_finalized (dir->dir, debug_name);
147 g_free (debug_name);
149 return dir->dir;
154 typedef struct {
155 XLExportBase base;
157 gint version;
158 gboolean with_extension;
160 Sheet const *sheet;
161 GHashTable *shared_string_hash;
162 GPtrArray *shared_string_array;
163 GHashTable *styles_hash;
164 GPtrArray *styles_array;
165 GHashTable *dxfs_hash;
166 GPtrArray *dxfs_array;
167 GnmConventions *convs;
168 GOIOContext *io_context;
169 GHashTable *axids;
171 GsfOutfile *xl_dir;
172 XLSXDir sheet_dir;
173 XLSXDir chart_dir, drawing_dir, legacy_drawing_dir;
174 XLSXDir media_dir, pivotCache_dir, pivotTable_dir;
175 unsigned comment;
176 unsigned drawing_elem_id;
177 GOFormat *date_fmt;
179 int custom_prop_id;
180 } XLSXWriteState;
182 typedef struct {
183 XLSXWriteState *state;
184 GsfXMLOut *xml;
185 } XLSXClosure;
187 typedef struct {
188 int code;
189 int width_mm;
190 gdouble width;
191 gdouble height;
192 GtkUnit unit;
193 } XLSXPaperDefs;
195 static void
196 xlsx_add_bool (GsfXMLOut *xml, char const *id, gboolean val)
198 gsf_xml_out_add_cstr_unchecked (xml, id, val ? "1" : "0");
200 static void
201 xlsx_add_rgb (GsfXMLOut *xml, char const *id, GOColor c)
203 char buf [3 * 4 * sizeof (unsigned int) + 1];
204 sprintf (buf, "%02X%02X%02X%02X",
205 GO_COLOR_UINT_A (c), GO_COLOR_UINT_R (c),
206 GO_COLOR_UINT_G (c), GO_COLOR_UINT_B (c));
207 gsf_xml_out_add_cstr_unchecked (xml, id, buf);
209 static void
210 xlsx_add_pos (GsfXMLOut *xml, char const *id, GnmCellPos const *pos)
212 gsf_xml_out_add_cstr_unchecked (xml, id,
213 cellpos_as_string (pos));
215 static void
216 xlsx_add_range (GsfXMLOut *xml, char const *id, GnmRange const *range)
218 gsf_xml_out_add_cstr_unchecked (xml, id,
219 range_as_string (range));
221 static void
222 xlsx_add_range_list (GsfXMLOut *xml, char const *id, GSList const *ranges)
224 GString *accum = g_string_new (NULL);
226 for (; NULL != ranges ; ranges = ranges->next) {
227 g_string_append (accum, range_as_string (ranges->data));
228 if (NULL != ranges->next)
229 g_string_append_c (accum, ' ');
232 gsf_xml_out_add_cstr_unchecked (xml, id, accum->str);
233 g_string_free (accum, TRUE);
235 static void
236 xlsx_add_pt (GsfXMLOut *xml, char const *id, double l)
238 GString *str = g_string_new (NULL);
240 g_string_append_printf (str, "%.2fpt", l);
241 gsf_xml_out_add_cstr_unchecked (xml, id, str->str);
242 g_string_free (str, TRUE);
245 /****************************************************************************/
247 #define N_PREDEFINED_FILLS (G_N_ELEMENTS (pre_def_fills) - 1)
249 static char const * const pats[] = {
250 "solid", /* 1 */
251 "darkGray", /* 2 */
252 "mediumGray", /* 3 */
253 "lightGray", /* 4 */
254 "gray125", /* 5 */
255 "gray0625", /* 6 */
256 "darkHorizontal", /* 7 */
257 "darkVertical", /* 8 */
258 "darkUp", /* 9 */
259 "darkDown", /* 10 */
260 "darkGrid", /* 11 */
261 "darkTrellis", /* 12 */
262 "lightHorizontal", /* 13 */
263 "lightVertical", /* 14 */
264 "lightDown", /* 15 */
265 "lightUp", /* 16 */
266 "lightGrid", /* 17 */
267 "lightTrellis", /* 18 */
268 /* Not in Excel */
269 "lightVertical", /* 19 */
270 "darkHorizontal", /* 20 */
271 "lightGray", /* 21 */
272 "lightGray", /* 22 */
273 "darkGray", /* 23 */
274 "solid", /* 24 */
277 static char const * const pre_def_fills[] = {
278 "none",
279 "gray125",
280 NULL
283 static void
284 xlsx_write_predefined_fills (GsfXMLOut *xml)
286 char const * const *f = pre_def_fills;
288 while (*f != NULL) {
289 gsf_xml_out_start_element (xml, "fill");
290 gsf_xml_out_start_element (xml, "patternFill");
291 gsf_xml_out_add_cstr_unchecked (xml, "patternType", *f++);
292 gsf_xml_out_end_element (xml);
293 gsf_xml_out_end_element (xml);
297 static gint
298 xlsx_find_predefined_fill (GnmStyle const *style)
300 gboolean pattern_is_std =
301 (!gnm_style_is_element_set (style, MSTYLE_COLOR_PATTERN) ||
302 gnm_style_get_pattern_color (style)->go_color == GO_COLOR_BLACK);
303 gboolean back_is_std =
304 (!gnm_style_is_element_set (style, MSTYLE_COLOR_BACK) ||
305 gnm_style_get_back_color (style)->go_color == GO_COLOR_WHITE);
307 if (gnm_style_is_element_set (style, MSTYLE_PATTERN) &&
308 gnm_style_get_pattern (style) == 0 &&
309 pattern_is_std &&
310 back_is_std)
311 return 0;
312 if (gnm_style_is_element_set (style, MSTYLE_PATTERN) &&
313 gnm_style_get_pattern (style) == 5 &&
314 pattern_is_std &&
315 back_is_std
317 return 1;
319 return -1;
322 /****************************************************************************/
324 static void
325 xlsx_write_rich_text (GsfXMLOut *xml, char const *text, PangoAttrList *attrs,
326 gboolean allow_xml_space)
328 PangoAttrIterator *iter;
329 int start, end, max;
331 if (attrs == NULL) {
332 gsf_xml_out_start_element (xml, "t");
333 gsf_xml_out_add_cstr (xml, NULL, text);
334 gsf_xml_out_end_element (xml); /* </t> */
335 return;
338 max = strlen (text);
339 iter = pango_attr_list_get_iterator (attrs);
340 do {
341 PangoAttribute *attr;
342 GOFontScript fs = GO_FONT_SCRIPT_STANDARD;
344 gsf_xml_out_start_element (xml, "r");
345 gsf_xml_out_start_element (xml, "rPr");
347 attr = pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY);
348 if (attr) {
349 gsf_xml_out_start_element (xml, "rFont");
350 gsf_xml_out_add_cstr_unchecked (xml, "val", ((PangoAttrString *) attr)->value);
351 gsf_xml_out_end_element (xml); /* </rFont> */
354 attr = pango_attr_iterator_get (iter, PANGO_ATTR_WEIGHT);
355 if (attr) {
356 gsf_xml_out_start_element (xml, "b");
357 gsf_xml_out_add_cstr_unchecked (xml, "val", ((PangoAttrInt *) attr)->value > PANGO_WEIGHT_NORMAL ? "true": "false");
358 gsf_xml_out_end_element (xml); /* </b> */
361 attr = pango_attr_iterator_get (iter, PANGO_ATTR_STYLE);
362 if (attr) {
363 gsf_xml_out_start_element (xml, "i");
364 gsf_xml_out_add_cstr_unchecked (xml, "val", ((PangoAttrInt *) attr)->value != PANGO_STYLE_NORMAL ? "true": "false");
365 gsf_xml_out_end_element (xml); /* </i> */
368 attr = pango_attr_iterator_get (iter, PANGO_ATTR_STRIKETHROUGH);
369 if (attr) {
370 gsf_xml_out_start_element (xml, "strike");
371 gsf_xml_out_add_cstr_unchecked (xml, "val", ((PangoAttrInt *) attr)->value ? "true": "false");
372 gsf_xml_out_end_element (xml); /* </strike> */
375 attr = pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND);
376 if (attr) {
377 PangoColor *color = &((PangoAttrColor *) attr)->color;
378 char *buf = g_strdup_printf("ff%02x%02x%02x", color->red >> 8, color->green >> 8, color->blue >> 8);
379 gsf_xml_out_start_element (xml, "color");
380 gsf_xml_out_add_cstr_unchecked (xml, "rgb", buf);
381 gsf_xml_out_end_element (xml); /* </color> */
382 g_free (buf);
385 attr = pango_attr_iterator_get (iter, PANGO_ATTR_SIZE);
386 if (attr) {
387 gsf_xml_out_start_element (xml, "sz");
388 gsf_xml_out_add_uint (xml, "val", ((PangoAttrInt *) attr)->value / PANGO_SCALE);
389 gsf_xml_out_end_element (xml); /* </sz> */
392 attr = pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
393 if (attr) {
394 PangoUnderline u = ((PangoAttrInt *) attr)->value;
395 gsf_xml_out_start_element (xml, "u");
396 switch (u) {
397 case PANGO_UNDERLINE_NONE:
398 default:
399 gsf_xml_out_add_cstr_unchecked (xml, "val", "none");
400 break;
401 case PANGO_UNDERLINE_ERROR:
402 /* not supported by OpenXML */
403 /* Fall through. */
404 case PANGO_UNDERLINE_SINGLE:
405 gsf_xml_out_add_cstr_unchecked (xml, "val", "single");
406 break;
407 case PANGO_UNDERLINE_DOUBLE:
408 gsf_xml_out_add_cstr_unchecked (xml, "val", "double");
409 break;
410 case PANGO_UNDERLINE_LOW:
411 gsf_xml_out_add_cstr_unchecked (xml, "val", "singleAccounting");
412 break;
414 gsf_xml_out_end_element (xml); /* </u> */
417 attr = pango_attr_iterator_get (iter, go_pango_attr_superscript_get_attr_type ());
418 if (attr && ((PangoAttrInt *) attr)->value)
419 fs = GO_FONT_SCRIPT_SUPER;
420 attr = pango_attr_iterator_get (iter, go_pango_attr_subscript_get_attr_type ());
421 if (attr && ((PangoAttrInt *) attr)->value)
422 fs = GO_FONT_SCRIPT_SUB;
423 if (fs != GO_FONT_SCRIPT_STANDARD) {
424 const char *va = (fs == GO_FONT_SCRIPT_SUB)
425 ? "subscript"
426 : "superscript";
427 gsf_xml_out_start_element (xml, "vertAlign");
428 gsf_xml_out_add_cstr_unchecked (xml, "val", va);
429 gsf_xml_out_end_element (xml); /* </vertAlign> */
432 gsf_xml_out_end_element (xml); /* </rPr> */
433 gsf_xml_out_start_element (xml, "t");
434 pango_attr_iterator_range (iter, &start, &end);
435 if (end > max)
436 end = max;
437 if (start < end) {
438 char *buf = g_strndup (text + start, end - start);
439 if (allow_xml_space) {
440 const char *p;
441 gboolean has_space = FALSE;
442 for (p = buf; *p; p = g_utf8_next_char (p)) {
443 if (g_unichar_isspace (g_utf8_get_char (p))) {
444 has_space = TRUE;
445 break;
448 if (has_space)
449 gsf_xml_out_add_cstr_unchecked
450 (xml, "xml:space", "preserve");
452 gsf_xml_out_add_cstr (xml, NULL, buf);
453 g_free (buf);
455 gsf_xml_out_end_element (xml); /* </t> */
456 gsf_xml_out_end_element (xml); /* </r> */
457 } while (pango_attr_iterator_next (iter));
458 pango_attr_iterator_destroy (iter);
461 static int
462 xlsx_shared_string (XLSXWriteState *state, GnmValue const *v)
464 gpointer tmp;
465 int i;
467 g_return_val_if_fail (VALUE_IS_STRING (v), -1);
469 if (g_hash_table_lookup_extended (state->shared_string_hash,
470 v, NULL, &tmp))
471 i = GPOINTER_TO_INT (tmp);
472 else {
473 GnmValue *v2 = value_dup (v);
475 if (VALUE_FMT (v2) && !go_format_is_markup (VALUE_FMT (v2))) {
476 value_set_fmt (v2, NULL);
477 i = xlsx_shared_string (state, v2);
478 value_release (v2);
479 } else {
480 i = state->shared_string_array->len;
481 g_ptr_array_add (state->shared_string_array, v2);
482 g_hash_table_insert (state->shared_string_hash,
483 v2, GINT_TO_POINTER (i));
487 return i;
490 static void
491 xlsx_write_shared_strings (XLSXWriteState *state, GsfOutfile *wb_part)
493 const unsigned N = state->shared_string_array->len;
494 unsigned i;
495 GsfOutput *part;
496 GsfXMLOut *xml;
498 if (N == 0)
499 return;
501 part = gsf_outfile_open_pkg_add_rel
502 (state->xl_dir, "sharedStrings.xml",
503 "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
504 wb_part,
505 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings");
506 xml = gsf_xml_out_new (part);
508 gsf_xml_out_start_element (xml, "sst");
509 gsf_xml_out_add_cstr_unchecked (xml, "xmlns", ns_ss);
510 gsf_xml_out_add_int (xml, "uniqueCount", N);
511 gsf_xml_out_add_int (xml, "count", N);
513 for (i = 0; i < N; i++) {
514 GnmValue const *val =
515 g_ptr_array_index (state->shared_string_array, i);
516 PangoAttrList *attrs = VALUE_FMT (val)
517 ? (PangoAttrList *)go_format_get_markup (VALUE_FMT (val))
518 : NULL;
519 gsf_xml_out_start_element (xml, "si");
520 xlsx_write_rich_text (xml,
521 value_peek_string (val),
522 attrs,
523 FALSE);
524 gsf_xml_out_end_element (xml); /* </si> */
527 gsf_xml_out_end_element (xml); /* </sst> */
529 g_object_unref (xml);
530 gsf_output_close (part);
531 g_object_unref (part);
534 static void
535 xlsx_load_buildin_num_formats (GHashTable *hash)
537 static char const * const std_builtins[] = {
538 /* 0 */ "General",
539 /* 1 */ "0",
540 /* 2 */ "0.00",
541 /* 3 */ "#,##0",
542 /* 4 */ "#,##0.00",
543 /* 5 */ NULL,
544 /* 6 */ NULL,
545 /* 7 */ NULL,
546 /* 8 */ NULL,
547 /* 9 */ "0%",
548 /* 10 */ "0.00%",
549 /* 11 */ "0.00E+00",
550 /* 12 */ "# ?/?",
551 /* 13 */ "# ?""?/?""?", /* silly trick to avoid using a trigraph */
552 /* 14 */ "mm-dd-yy",
553 /* 15 */ "d-mmm-yy",
554 /* 16 */ "d-mmm",
555 /* 17 */ "mmm-yy",
556 /* 18 */ "h:mm AM/PM",
557 /* 19 */ "h:mm:ss AM/PM",
558 /* 20 */ "h:mm",
559 /* 21 */ "h:mm:ss",
560 /* 22 */ "m/d/yy h:mm",
561 /* 23 */ NULL,
562 /* 24 */ NULL,
563 /* 25 */ NULL,
564 /* 26 */ NULL,
565 /* 27 */ NULL,
566 /* 28 */ NULL,
567 /* 29 */ NULL,
568 /* 30 */ NULL,
569 /* 31 */ NULL,
570 /* 32 */ NULL,
571 /* 33 */ NULL,
572 /* 34 */ NULL,
573 /* 35 */ NULL,
574 /* 36 */ NULL,
575 /* 37 */ "#,##0 ;(#,##0)",
576 /* 38 */ "#,##0 ;[Red](#,##0)",
577 /* 39 */ "#,##0.00;(#,##0.00)",
578 /* 40 */ "#,##0.00;[Red](#,##0.00)",
579 /* 41 */ NULL,
580 /* 42 */ NULL,
581 /* 43 */ NULL,
582 /* 44 */ NULL,
583 /* 45 */ "mm:ss",
584 /* 46 */ "[h]:mm:ss",
585 /* 47 */ "mmss.0",
586 /* 48 */ "##0.0E+0",
587 /* 49 */ "@"
589 unsigned int i;
591 g_return_if_fail (NUM_FORMAT_BASE > (int) G_N_ELEMENTS (std_builtins));
593 for (i = 1; i < G_N_ELEMENTS (std_builtins); i++)
594 if (std_builtins[i] != NULL)
595 g_hash_table_insert (hash, g_strdup (std_builtins[i]), GUINT_TO_POINTER (i));
598 static void
599 xlsx_write_num_format (XLSXWriteState *state, GsfXMLOut *xml,
600 GOFormat const *format, int id)
602 gsf_xml_out_start_element (xml, "numFmt");
603 gsf_xml_out_add_cstr (xml, "formatCode", go_format_as_XL (format));
604 gsf_xml_out_add_int (xml, "numFmtId", id);
605 gsf_xml_out_end_element (xml);
608 static GHashTable *
609 xlsx_write_num_formats (XLSXWriteState *state, GsfXMLOut *xml)
611 GHashTable *hash = g_hash_table_new_full
612 (g_str_hash, g_str_equal, g_free, NULL);
613 GHashTable *num_format_hash = g_hash_table_new
614 (g_direct_hash, g_direct_equal);
615 unsigned int i, count;
616 GPtrArray *num_format_array = g_ptr_array_new ();
618 xlsx_load_buildin_num_formats (hash);
620 for (i = 0 ; i < state->styles_array->len ; i++) {
621 GnmStyle const *style = g_ptr_array_index (state->styles_array, i);
622 GOFormat const *format;
623 char const *xl;
624 gpointer tmp;
626 if (!gnm_style_is_element_set (style, MSTYLE_FORMAT))
627 continue;
628 format = gnm_style_get_format (style);
629 if (go_format_is_general (format))
630 continue;
631 xl = go_format_as_XL (format);
632 tmp = g_hash_table_lookup (hash, xl);
633 if (tmp == NULL) {
634 tmp = GUINT_TO_POINTER (num_format_array->len + NUM_FORMAT_BASE);
635 g_hash_table_insert (hash, g_strdup (xl), tmp);
636 g_ptr_array_add (num_format_array, (gpointer)format);
638 g_hash_table_insert (num_format_hash, (gpointer) style, tmp);
641 count = num_format_array->len;
642 if (count != 0) {
643 gsf_xml_out_start_element (xml, "numFmts");
644 gsf_xml_out_add_int (xml, "count", count);
645 for (i = 0; i < count; i++) {
646 GOFormat const *format =
647 g_ptr_array_index (num_format_array, i);
648 xlsx_write_num_format (state, xml, format,
649 i + NUM_FORMAT_BASE);
651 gsf_xml_out_end_element (xml);
653 g_ptr_array_free (num_format_array, TRUE);
654 g_hash_table_destroy (hash);
655 return num_format_hash;
658 static void
659 xlsx_write_color_element (GsfXMLOut *xml, char const *id, GOColor color)
661 gsf_xml_out_start_element (xml, id);
662 xlsx_add_rgb (xml, "rgb", color);
663 gsf_xml_out_end_element (xml);
666 static gint
667 xlsx_find_font (GnmStyle const *style, GPtrArray *styles)
669 unsigned i;
670 for (i = 0 ; i < styles->len ; i++) {
671 GnmStyle const *old_style = g_ptr_array_index (styles, i);
672 if (style == old_style ||
673 (gnm_style_equal_elem (style, old_style, MSTYLE_FONT_BOLD) &&
674 gnm_style_equal_elem (style, old_style, MSTYLE_FONT_ITALIC) &&
675 gnm_style_equal_elem (style, old_style, MSTYLE_FONT_UNDERLINE) &&
676 gnm_style_equal_elem (style, old_style, MSTYLE_FONT_COLOR) &&
677 gnm_style_equal_elem (style, old_style, MSTYLE_FONT_SIZE) &&
678 gnm_style_equal_elem (style, old_style, MSTYLE_FONT_SCRIPT) &&
679 gnm_style_equal_elem (style, old_style, MSTYLE_FONT_STRIKETHROUGH) &&
680 gnm_style_equal_elem (style, old_style, MSTYLE_FONT_NAME)))
681 return (gint) i;
683 return -1;
686 static gboolean
687 xlsx_has_font_style (GnmStyle const *style)
689 return (gnm_style_is_element_set (style, MSTYLE_FONT_BOLD) ||
690 gnm_style_is_element_set (style, MSTYLE_FONT_ITALIC) ||
691 gnm_style_is_element_set (style, MSTYLE_FONT_UNDERLINE) ||
692 gnm_style_is_element_set (style, MSTYLE_FONT_COLOR) ||
693 gnm_style_is_element_set (style, MSTYLE_FONT_NAME) ||
694 gnm_style_is_element_set (style, MSTYLE_FONT_SCRIPT) ||
695 gnm_style_is_element_set (style, MSTYLE_FONT_SIZE) ||
696 gnm_style_is_element_set (style, MSTYLE_FONT_STRIKETHROUGH));
699 static void
700 xlsx_write_font (XLSXWriteState *state, GsfXMLOut *xml, GnmStyle const *style)
702 gsf_xml_out_start_element (xml, "font");
703 if (gnm_style_is_element_set (style, MSTYLE_FONT_BOLD)) {
704 gsf_xml_out_start_element (xml, "b");
705 xlsx_add_bool (xml, "val", gnm_style_get_font_bold (style));
706 gsf_xml_out_end_element (xml);
708 if (gnm_style_is_element_set (style, MSTYLE_FONT_ITALIC)) {
709 gsf_xml_out_start_element (xml, "i");
710 xlsx_add_bool (xml, "val", gnm_style_get_font_italic (style));
711 gsf_xml_out_end_element (xml);
713 if (gnm_style_is_element_set (style, MSTYLE_FONT_UNDERLINE)) {
714 static const char * const types[] = {
715 "none",
716 "single",
717 "double",
718 "singleAccounting",
719 "doubleAccounting"
721 unsigned uline = gnm_style_get_font_uline (style);
723 if (uline < G_N_ELEMENTS (types)) {
724 gsf_xml_out_start_element (xml, "u");
725 gsf_xml_out_add_cstr
726 (xml, "val", types[gnm_style_get_font_uline (style)]);
727 gsf_xml_out_end_element (xml);
730 if (gnm_style_is_element_set (style, MSTYLE_FONT_COLOR))
731 xlsx_write_color_element
732 (xml, "color",
733 gnm_style_get_font_color (style)->go_color);
734 if (gnm_style_is_element_set (style, MSTYLE_FONT_NAME)) {
735 gsf_xml_out_start_element (xml, "name");
736 gsf_xml_out_add_cstr
737 (xml, "val", gnm_style_get_font_name (style));
738 gsf_xml_out_end_element (xml);
740 if (gnm_style_is_element_set (style, MSTYLE_FONT_SCRIPT)) {
741 GOFontScript scr = gnm_style_get_font_script (style);
742 const char *val;
744 switch (scr) {
745 case GO_FONT_SCRIPT_SUB: val = "subscript"; break;
746 case GO_FONT_SCRIPT_SUPER: val = "superscript"; break;
747 default:
748 case GO_FONT_SCRIPT_STANDARD: val = "baseline"; break;
750 gsf_xml_out_start_element (xml, "vertAlign");
751 gsf_xml_out_add_cstr (xml, "val", val);
752 gsf_xml_out_end_element (xml);
754 if (gnm_style_is_element_set (style, MSTYLE_FONT_SIZE)) {
755 gsf_xml_out_start_element (xml, "sz");
756 go_xml_out_add_double
757 (xml, "val", gnm_style_get_font_size (style));
758 gsf_xml_out_end_element (xml);
761 if (gnm_style_is_element_set (style, MSTYLE_FONT_STRIKETHROUGH)) {
762 gsf_xml_out_start_element (xml, "strike");
763 xlsx_add_bool (xml, "val", gnm_style_get_font_strike (style));
764 gsf_xml_out_end_element (xml);
766 gsf_xml_out_end_element (xml); /* font */
769 static GHashTable *
770 xlsx_write_fonts (XLSXWriteState *state, GsfXMLOut *xml)
772 GHashTable *fonts_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
773 GPtrArray *styles_w_fonts = g_ptr_array_new ();
774 unsigned int i;
776 for (i = 0 ; i < state->styles_array->len ; i++) {
777 GnmStyle const *style = g_ptr_array_index (state->styles_array, i);
778 if (xlsx_has_font_style (style)) {
779 gint font_n = xlsx_find_font (style, styles_w_fonts);
780 if (font_n < 0) {
781 g_ptr_array_add (styles_w_fonts, (gpointer)style);
782 g_hash_table_insert (fonts_hash, (gpointer)style,
783 GINT_TO_POINTER (styles_w_fonts->len));
784 } else
785 g_hash_table_insert (fonts_hash, (gpointer)style,
786 GINT_TO_POINTER (font_n + 1));
790 if (styles_w_fonts->len > 0) {
791 gsf_xml_out_start_element (xml, "fonts");
792 gsf_xml_out_add_int (xml, "count", styles_w_fonts->len);
793 for (i = 0 ; i < styles_w_fonts->len ; i++) {
794 GnmStyle const *style = g_ptr_array_index (styles_w_fonts, i);
795 xlsx_write_font (state, xml, style);
797 gsf_xml_out_end_element (xml);
800 g_ptr_array_free (styles_w_fonts, TRUE);
801 return fonts_hash;
804 static gint
805 xlsx_find_fill (GnmStyle const *style, GPtrArray *styles)
807 unsigned i;
808 int j;
810 j = xlsx_find_predefined_fill (style);
811 if (j >= 0)
812 return j;
813 for (i = 0 ; i < styles->len ; i++) {
814 GnmStyle const *old_style = g_ptr_array_index (styles, i);
815 if (style == old_style ||
816 (gnm_style_equal_elem (style, old_style, MSTYLE_COLOR_BACK) &&
817 gnm_style_equal_elem (style, old_style, MSTYLE_COLOR_PATTERN) &&
818 gnm_style_equal_elem (style, old_style, MSTYLE_PATTERN)))
819 return (gint) i + N_PREDEFINED_FILLS;
821 return -1;
824 static gboolean
825 xlsx_has_background_style (GnmStyle const *style)
827 return (gnm_style_is_element_set (style, MSTYLE_COLOR_BACK) ||
828 gnm_style_is_element_set (style, MSTYLE_COLOR_PATTERN) ||
829 gnm_style_is_element_set (style, MSTYLE_PATTERN));
832 static void
833 xlsx_write_background (XLSXWriteState *state, GsfXMLOut *xml,
834 GnmStyle const *style, gboolean invert_solid)
837 * MAGIC:
838 * Looks like pattern background and forground colours are inverted
839 * for dxfs with solid fills for no apparent reason.
841 gboolean invert = FALSE;
842 GnmColor *fg;
843 GnmColor *bg;
845 gsf_xml_out_start_element (xml, "fill");
846 gsf_xml_out_start_element (xml, "patternFill");
847 if (gnm_style_is_element_set (style, MSTYLE_PATTERN)) {
848 gint pattern = gnm_style_get_pattern (style);
849 const char *type;
850 if (pattern <= 0 || pattern > (gint)G_N_ELEMENTS (pats)) {
851 type = "none";
852 } else {
853 invert = (pattern == 1 && invert_solid);
854 type = pats[pattern - 1];
856 gsf_xml_out_add_cstr_unchecked (xml, "patternType", type);
859 fg = gnm_style_is_element_set (style, MSTYLE_COLOR_BACK)
860 ? gnm_style_get_back_color (style)
861 : NULL;
862 bg = gnm_style_is_element_set (style, MSTYLE_COLOR_PATTERN)
863 ? gnm_style_get_pattern_color (style)
864 : NULL;
865 if (invert) {
866 GnmColor *tmp = fg;
867 fg = bg;
868 bg = tmp;
871 if (fg)
872 xlsx_write_color_element (xml, "fgColor", fg->go_color);
873 if (bg)
874 xlsx_write_color_element (xml, "bgColor", bg->go_color);
876 gsf_xml_out_end_element (xml);
877 gsf_xml_out_end_element (xml);
880 static GHashTable *
881 xlsx_write_fills (XLSXWriteState *state, GsfXMLOut *xml)
883 GHashTable *fills_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
884 GPtrArray *styles_w_fills = g_ptr_array_new ();
885 unsigned i;
887 for (i = 0 ; i < state->styles_array->len ; i++) {
888 GnmStyle const *style = g_ptr_array_index (state->styles_array, i);
889 if (xlsx_has_background_style (style)) {
890 gint fill_n = xlsx_find_fill (style, styles_w_fills);
891 if (fill_n < 0) {
892 g_ptr_array_add (styles_w_fills, (gpointer)style);
893 g_hash_table_insert
894 (fills_hash, (gpointer)style,
895 GINT_TO_POINTER (styles_w_fills->len
896 + N_PREDEFINED_FILLS));
897 } else
898 g_hash_table_insert
899 (fills_hash, (gpointer)style,
900 GINT_TO_POINTER
901 (fill_n + 1));
905 gsf_xml_out_start_element (xml, "fills");
906 gsf_xml_out_add_int (xml, "count", styles_w_fills->len
907 + N_PREDEFINED_FILLS);
908 /* Excel considers the first few fills special (not according to */
909 /* ECMA) */
910 xlsx_write_predefined_fills (xml);
911 for (i = 0 ; i < styles_w_fills->len ; i++) {
912 GnmStyle const *style = g_ptr_array_index (styles_w_fills, i);
913 xlsx_write_background (state, xml, style, FALSE);
915 gsf_xml_out_end_element (xml);
917 g_ptr_array_free (styles_w_fills, TRUE);
918 return fills_hash;
921 static gint
922 xlsx_find_border (GnmStyle const *style, GPtrArray *styles)
924 unsigned i;
925 for (i = 0 ; i < styles->len ; i++) {
926 GnmStyle const *old_style = g_ptr_array_index (styles, i);
927 if (style == old_style ||
928 (gnm_style_equal_elem (style, old_style, MSTYLE_BORDER_TOP) &&
929 gnm_style_equal_elem (style, old_style, MSTYLE_BORDER_BOTTOM) &&
930 gnm_style_equal_elem (style, old_style, MSTYLE_BORDER_LEFT) &&
931 gnm_style_equal_elem (style, old_style, MSTYLE_BORDER_RIGHT) &&
932 gnm_style_equal_elem (style, old_style, MSTYLE_BORDER_DIAGONAL) &&
933 gnm_style_equal_elem (style, old_style, MSTYLE_BORDER_REV_DIAGONAL)))
934 return (gint) i;
936 return -1;
939 static void
940 xlsx_write_border (XLSXWriteState *state, GsfXMLOut *xml, GnmBorder *border, GnmStyleElement elem)
942 if (border == NULL)
943 return;
944 switch (elem) {
945 case MSTYLE_BORDER_TOP:
946 gsf_xml_out_start_element (xml, "top");
947 break;
948 case MSTYLE_BORDER_BOTTOM:
949 gsf_xml_out_start_element (xml, "bottom");
950 break;
951 case MSTYLE_BORDER_LEFT:
952 gsf_xml_out_start_element
953 (xml, (state->version == ECMA_376_2006) ? "left" : "start");
954 break;
955 case MSTYLE_BORDER_DIAGONAL:
956 case MSTYLE_BORDER_REV_DIAGONAL:
957 gsf_xml_out_start_element (xml, "diagonal");
958 break;
959 default:
960 case MSTYLE_BORDER_RIGHT:
961 gsf_xml_out_start_element
962 (xml, (state->version == ECMA_376_2006) ? "right" : "end");
963 break;
965 switch (border->line_type) {
966 case GNM_STYLE_BORDER_THIN:
967 gsf_xml_out_add_cstr_unchecked (xml, "style", "thin");
968 break;
969 case GNM_STYLE_BORDER_MEDIUM:
970 gsf_xml_out_add_cstr_unchecked (xml, "style", "medium");
971 break;
972 case GNM_STYLE_BORDER_DASHED:
973 gsf_xml_out_add_cstr_unchecked (xml, "style", "dashed");
974 break;
975 case GNM_STYLE_BORDER_DOTTED:
976 gsf_xml_out_add_cstr_unchecked (xml, "style", "dotted");
977 break;
978 case GNM_STYLE_BORDER_THICK:
979 gsf_xml_out_add_cstr_unchecked (xml, "style", "thick");
980 break;
981 case GNM_STYLE_BORDER_DOUBLE:
982 gsf_xml_out_add_cstr_unchecked (xml, "style", "double");
983 break;
984 case GNM_STYLE_BORDER_HAIR:
985 gsf_xml_out_add_cstr_unchecked (xml, "style", "hair");
986 break;
987 case GNM_STYLE_BORDER_MEDIUM_DASH:
988 gsf_xml_out_add_cstr_unchecked (xml, "style", "mediumDashed");
989 break;
990 case GNM_STYLE_BORDER_DASH_DOT:
991 gsf_xml_out_add_cstr_unchecked (xml, "style", "dashDot");
992 break;
993 case GNM_STYLE_BORDER_MEDIUM_DASH_DOT:
994 gsf_xml_out_add_cstr_unchecked (xml, "style", "mediumDashDot");
995 break;
996 case GNM_STYLE_BORDER_DASH_DOT_DOT:
997 gsf_xml_out_add_cstr_unchecked (xml, "style", "dashDotDot");
998 break;
999 case GNM_STYLE_BORDER_MEDIUM_DASH_DOT_DOT:
1000 gsf_xml_out_add_cstr_unchecked (xml, "style", "mediumDashDotDot");
1001 break;
1002 case GNM_STYLE_BORDER_SLANTED_DASH_DOT:
1003 gsf_xml_out_add_cstr_unchecked (xml, "style", "slantDashDot");
1004 break;
1005 default:
1006 case GNM_STYLE_BORDER_NONE:
1007 gsf_xml_out_add_cstr_unchecked (xml, "style", "none");
1008 break;
1011 if (border->color != NULL)
1012 xlsx_write_color_element (xml, "color", border->color->go_color);
1014 gsf_xml_out_end_element (xml);
1017 static gboolean
1018 xlsx_has_border_style (GnmStyle const *style)
1020 return (gnm_style_is_element_set (style, MSTYLE_BORDER_TOP) ||
1021 gnm_style_is_element_set (style, MSTYLE_BORDER_BOTTOM) ||
1022 gnm_style_is_element_set (style, MSTYLE_BORDER_LEFT) ||
1023 gnm_style_is_element_set (style, MSTYLE_BORDER_RIGHT) ||
1024 gnm_style_is_element_set (style, MSTYLE_BORDER_REV_DIAGONAL) ||
1025 gnm_style_is_element_set (style, MSTYLE_BORDER_DIAGONAL));
1028 static void
1029 xlsx_write_full_border (XLSXWriteState *state, GsfXMLOut *xml,
1030 GnmStyle const *style)
1032 gboolean diagonal_border_written = FALSE;
1033 gsf_xml_out_start_element (xml, "border");
1034 if (gnm_style_is_element_set (style, MSTYLE_BORDER_DIAGONAL)) {
1035 GnmBorder *border = gnm_style_get_border
1036 (style, MSTYLE_BORDER_DIAGONAL);
1037 gsf_xml_out_add_bool
1038 (xml, "diagonalUp",
1039 (border && border->line_type != GNM_STYLE_BORDER_NONE));
1041 if (gnm_style_is_element_set (style, MSTYLE_BORDER_REV_DIAGONAL)) {
1042 GnmBorder *border = gnm_style_get_border
1043 (style, MSTYLE_BORDER_REV_DIAGONAL);
1044 gsf_xml_out_add_bool
1045 (xml, "diagonalDown",
1046 (border && border->line_type != GNM_STYLE_BORDER_NONE));
1048 if (gnm_style_is_element_set (style, MSTYLE_BORDER_LEFT))
1049 xlsx_write_border (state, xml,
1050 gnm_style_get_border
1051 (style, MSTYLE_BORDER_LEFT),
1052 MSTYLE_BORDER_LEFT);
1053 if (gnm_style_is_element_set (style, MSTYLE_BORDER_RIGHT))
1054 xlsx_write_border (state, xml,
1055 gnm_style_get_border
1056 (style, MSTYLE_BORDER_RIGHT),
1057 MSTYLE_BORDER_RIGHT);
1058 if (gnm_style_is_element_set (style, MSTYLE_BORDER_TOP))
1059 xlsx_write_border (state, xml,
1060 gnm_style_get_border
1061 (style, MSTYLE_BORDER_TOP),
1062 MSTYLE_BORDER_TOP);
1063 if (gnm_style_is_element_set (style, MSTYLE_BORDER_BOTTOM))
1064 xlsx_write_border (state, xml,
1065 gnm_style_get_border
1066 (style, MSTYLE_BORDER_BOTTOM),
1067 MSTYLE_BORDER_BOTTOM);
1068 if (gnm_style_is_element_set (style, MSTYLE_BORDER_DIAGONAL)) {
1069 GnmBorder *border = gnm_style_get_border
1070 (style, MSTYLE_BORDER_DIAGONAL);
1071 if (border && border->line_type != GNM_STYLE_BORDER_NONE) {
1072 diagonal_border_written = TRUE;
1073 xlsx_write_border (state, xml,
1074 border,
1075 MSTYLE_BORDER_DIAGONAL);
1078 if (!diagonal_border_written &&
1079 gnm_style_is_element_set (style, MSTYLE_BORDER_REV_DIAGONAL)) {
1080 GnmBorder *border = gnm_style_get_border
1081 (style, MSTYLE_BORDER_REV_DIAGONAL);
1082 if (border && border->line_type != GNM_STYLE_BORDER_NONE) {
1083 xlsx_write_border (state, xml,
1084 border,
1085 MSTYLE_BORDER_REV_DIAGONAL);
1088 gsf_xml_out_end_element (xml); /* border */
1092 static GHashTable *
1093 xlsx_write_borders (XLSXWriteState *state, GsfXMLOut *xml)
1095 GHashTable *border_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
1096 GPtrArray *styles_w_border = g_ptr_array_new ();
1097 unsigned int i;
1099 for (i = 0 ; i < state->styles_array->len ; i++) {
1100 GnmStyle const *style = g_ptr_array_index (state->styles_array, i);
1101 if (xlsx_has_border_style (style)) {
1102 gint border_n = xlsx_find_border (style, styles_w_border);
1103 if (border_n < 0) {
1104 g_ptr_array_add (styles_w_border, (gpointer)style);
1105 g_hash_table_insert (border_hash, (gpointer)style,
1106 GINT_TO_POINTER (styles_w_border->len));
1107 } else
1108 g_hash_table_insert (border_hash, (gpointer)style,
1109 GINT_TO_POINTER (border_n + 1));
1113 if (styles_w_border->len > 0) {
1114 gsf_xml_out_start_element (xml, "borders");
1115 gsf_xml_out_add_int (xml, "count", styles_w_border->len);
1116 for (i = 0; i < styles_w_border->len; i++) {
1117 GnmStyle const *style = g_ptr_array_index (styles_w_border, i);
1118 xlsx_write_full_border (state, xml, style);
1120 gsf_xml_out_end_element (xml);
1123 g_ptr_array_free (styles_w_border, TRUE);
1124 return border_hash;
1127 static gboolean
1128 xlsx_has_alignment_style (GnmStyle const *style)
1130 return gnm_style_is_element_set (style, MSTYLE_ALIGN_H)
1131 || gnm_style_is_element_set (style, MSTYLE_ALIGN_V)
1132 || gnm_style_is_element_set (style, MSTYLE_WRAP_TEXT)
1133 || gnm_style_is_element_set (style, MSTYLE_SHRINK_TO_FIT)
1134 || gnm_style_is_element_set (style, MSTYLE_ROTATION)
1135 || gnm_style_is_element_set (style, MSTYLE_INDENT);
1138 static void
1139 xlsx_write_style_write_alignment (G_GNUC_UNUSED XLSXWriteState *state, GsfXMLOut *xml,
1140 GnmStyle const *style)
1142 gsf_xml_out_start_element (xml, "alignment");
1143 if (gnm_style_is_element_set (style, MSTYLE_ALIGN_H)) {
1144 switch (gnm_style_get_align_h (style)) {
1145 case GNM_HALIGN_LEFT:
1146 gsf_xml_out_add_cstr_unchecked (xml, "horizontal",
1147 "left");
1148 break;
1149 case GNM_HALIGN_RIGHT:
1150 gsf_xml_out_add_cstr_unchecked (xml, "horizontal",
1151 "right");
1152 break;
1153 case GNM_HALIGN_CENTER:
1154 gsf_xml_out_add_cstr_unchecked (xml, "horizontal",
1155 "center");
1156 break;
1157 case GNM_HALIGN_FILL:
1158 gsf_xml_out_add_cstr_unchecked (xml, "horizontal",
1159 "fill");
1160 break;
1161 case GNM_HALIGN_JUSTIFY:
1162 gsf_xml_out_add_cstr_unchecked (xml, "horizontal",
1163 "justify");
1164 break;
1165 case GNM_HALIGN_CENTER_ACROSS_SELECTION:
1166 gsf_xml_out_add_cstr_unchecked (xml, "horizontal",
1167 "centerContinuous");
1168 break;
1169 case GNM_HALIGN_DISTRIBUTED:
1170 gsf_xml_out_add_cstr_unchecked (xml, "horizontal",
1171 "distributed");
1172 break;
1173 case GNM_HALIGN_GENERAL:
1174 default:
1175 gsf_xml_out_add_cstr_unchecked (xml, "horizontal",
1176 "general");
1177 break;
1180 if (gnm_style_is_element_set (style, MSTYLE_ALIGN_V)) {
1181 switch (gnm_style_get_align_v (style)) {
1182 case GNM_VALIGN_TOP:
1183 gsf_xml_out_add_cstr_unchecked (xml, "vertical",
1184 "top");
1185 break;
1186 case GNM_VALIGN_BOTTOM:
1187 gsf_xml_out_add_cstr_unchecked (xml, "vertical",
1188 "bottom");
1189 break;
1190 case GNM_VALIGN_CENTER:
1191 gsf_xml_out_add_cstr_unchecked (xml, "vertical",
1192 "center");
1193 break;
1194 case GNM_VALIGN_JUSTIFY:
1195 gsf_xml_out_add_cstr_unchecked (xml, "vertical",
1196 "justify");
1197 break;
1198 default:
1199 case GNM_VALIGN_DISTRIBUTED:
1200 gsf_xml_out_add_cstr_unchecked (xml, "vertical",
1201 "distributed");
1202 break;
1205 if (gnm_style_is_element_set (style, MSTYLE_WRAP_TEXT)) {
1206 gsf_xml_out_add_bool (xml, "wrapText", gnm_style_get_wrap_text (style));
1208 if (gnm_style_is_element_set (style, MSTYLE_SHRINK_TO_FIT)) {
1209 gsf_xml_out_add_bool (xml, "shrinkToFit", gnm_style_get_shrink_to_fit (style));
1211 if (gnm_style_is_element_set (style, MSTYLE_ROTATION)) {
1212 int r = gnm_style_get_rotation (style);
1213 if (r == -1)
1214 r = 0xff;
1215 else if (r >= 270)
1216 r = (360 - r) + 90;
1217 gsf_xml_out_add_int (xml, "textRotation", r);
1219 if (gnm_style_is_element_set (style, MSTYLE_INDENT)) {
1220 gsf_xml_out_add_int (xml, "indent", gnm_style_get_indent (style));
1222 gsf_xml_out_end_element (xml);
1225 static void
1226 xlsx_write_style (XLSXWriteState *state, GsfXMLOut *xml,
1227 GnmStyle const *style, GHashTable *fills_hash,
1228 GHashTable *num_format_hash, GHashTable *fonts_hash,
1229 GHashTable *border_hash, gint id)
1231 gboolean alignment = xlsx_has_alignment_style (style);
1232 gpointer tmp_fill, tmp_font, tmp_border;
1233 gboolean fill = (NULL != (tmp_fill = g_hash_table_lookup (fills_hash, style)));
1234 gboolean font = (NULL != (tmp_font = g_hash_table_lookup (fonts_hash, style)));
1235 gboolean border = (NULL != (tmp_border = g_hash_table_lookup (border_hash, style)));
1236 gboolean num_fmt = gnm_style_is_element_set (style, MSTYLE_FORMAT);
1238 if (id >= 0) {
1239 xlsx_add_bool (xml, "applyAlignment", alignment);
1240 xlsx_add_bool (xml, "applyBorder", border);
1241 xlsx_add_bool (xml, "applyFont", font);
1242 xlsx_add_bool (xml, "applyFill", fill);
1243 xlsx_add_bool (xml, "applyNumberFormat", num_fmt);
1245 if (font)
1246 gsf_xml_out_add_int (xml, "fontId", GPOINTER_TO_INT (tmp_font) - 1);
1247 if (fill)
1248 gsf_xml_out_add_int (xml, "fillId", GPOINTER_TO_INT (tmp_fill) - 1);
1249 if (border)
1250 gsf_xml_out_add_int (xml, "borderId", GPOINTER_TO_INT (tmp_border) - 1);
1251 if (num_fmt)
1252 gsf_xml_out_add_int
1253 (xml, "numFmtId",
1254 GPOINTER_TO_INT (g_hash_table_lookup (num_format_hash, style)));
1255 if (id >= 0)
1256 gsf_xml_out_add_int (xml, "xfId", id);
1258 if (alignment)
1259 xlsx_write_style_write_alignment (state, xml, style);
1262 static void
1263 xlsx_write_cellStyleXfs (XLSXWriteState *state, GsfXMLOut *xml,
1264 GHashTable *fills_hash, GHashTable *num_format_hash,
1265 GHashTable *fonts_hash, GHashTable *border_hash)
1267 gsf_xml_out_start_element (xml, "cellStyleXfs");
1268 gsf_xml_out_add_int (xml, "count", 1);
1269 gsf_xml_out_start_element (xml, "xf");
1270 xlsx_write_style
1271 (state, xml,
1272 g_ptr_array_index (state->styles_array, 0),
1273 fills_hash, num_format_hash, fonts_hash,
1274 border_hash, -1);
1275 gsf_xml_out_end_element (xml);
1276 gsf_xml_out_end_element (xml);
1279 static void
1280 xlsx_write_cellXfs (XLSXWriteState *state, GsfXMLOut *xml,
1281 GHashTable *fills_hash, GHashTable *num_format_hash,
1282 GHashTable *fonts_hash, GHashTable *border_hash)
1284 unsigned i, N = state->styles_array->len;
1286 if (N == 0)
1287 return;
1289 gsf_xml_out_start_element (xml, "cellXfs");
1290 gsf_xml_out_add_int (xml, "count", N);
1291 for (i = 0; i < N; i++) {
1292 GnmStyle const *style =
1293 g_ptr_array_index (state->styles_array, i);
1294 gsf_xml_out_start_element (xml, "xf");
1295 xlsx_write_style
1296 (state, xml, style,
1297 fills_hash, num_format_hash, fonts_hash,
1298 border_hash, 0);
1299 gsf_xml_out_end_element (xml);
1301 gsf_xml_out_end_element (xml);
1304 static void
1305 xlsx_write_dxfs (XLSXWriteState *state, GsfXMLOut *xml,
1306 GHashTable *num_format_hash)
1308 unsigned i, N = state->dxfs_array->len;
1309 int id = NUM_FORMAT_BASE + g_hash_table_size (num_format_hash);
1311 if (N == 0)
1312 return;
1314 gsf_xml_out_start_element (xml, "dxfs");
1315 gsf_xml_out_add_int (xml, "count", N);
1316 for (i = 0; i < N; i++) {
1317 GnmStyle *style =
1318 g_ptr_array_index (state->dxfs_array, i);
1319 gsf_xml_out_start_element (xml, "dxf");
1320 if (xlsx_has_font_style (style))
1321 xlsx_write_font (state, xml, style);
1322 if (xlsx_has_alignment_style (style))
1323 xlsx_write_style_write_alignment (state, xml, style);
1324 if (xlsx_has_background_style (style))
1325 xlsx_write_background (state, xml, style, TRUE);
1326 if (gnm_style_is_element_set (style, MSTYLE_FORMAT)) {
1327 GOFormat const *format = gnm_style_get_format (style);
1328 xlsx_write_num_format (state, xml, format, id++);
1330 if (xlsx_has_border_style (style))
1331 xlsx_write_full_border (state, xml, style);
1333 gsf_xml_out_end_element (xml);
1335 gsf_xml_out_end_element (xml);
1338 static void
1339 xlsx_write_styles (XLSXWriteState *state, GsfOutfile *wb_part)
1341 GsfOutput *part = gsf_outfile_open_pkg_add_rel (state->xl_dir, "styles.xml",
1342 "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
1343 wb_part,
1344 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles");
1345 GsfXMLOut *xml = gsf_xml_out_new (part);
1346 GHashTable *fills_hash, *num_format_hash, *fonts_hash, *border_hash;
1348 gsf_xml_out_start_element (xml, "styleSheet");
1349 gsf_xml_out_add_cstr_unchecked (xml, "xmlns", ns_ss);
1350 /* Note the schema does not allow the attribute xml:space */
1352 /* The order of elements is fixed in the schema (xsd:sequence) */
1353 num_format_hash = xlsx_write_num_formats (state, xml);
1354 fonts_hash = xlsx_write_fonts (state, xml);
1355 fills_hash = xlsx_write_fills (state, xml);
1356 border_hash = xlsx_write_borders (state, xml);
1357 xlsx_write_cellStyleXfs (state, xml, fills_hash, num_format_hash, fonts_hash, border_hash);
1358 xlsx_write_cellXfs (state, xml, fills_hash, num_format_hash, fonts_hash, border_hash);
1359 /* <xsd:element name="cellStyles" type="CT_CellStyles" minOccurs="0" maxOccurs="1"/> */
1360 xlsx_write_dxfs (state, xml, num_format_hash);
1361 /* <xsd:element name="tableStyles" type="CT_TableStyles" minOccurs="0" maxOccurs="1"/> */
1362 /* <xsd:element name="colors" type="CT_Colors" minOccurs="0" maxOccurs="1"/> */
1363 /* <xsd:element name="extLst" type="CT_ExtensionList" minOccurs="0" maxOccurs="1"/> */
1365 g_hash_table_destroy (fills_hash);
1366 g_hash_table_destroy (fonts_hash);
1367 g_hash_table_destroy (num_format_hash);
1368 g_hash_table_destroy (border_hash);
1369 gsf_xml_out_end_element (xml); /* </styleSheet> */
1370 g_object_unref (xml);
1371 gsf_output_close (part);
1372 g_object_unref (part);
1375 #define XLSX_MAX_COLS gnm_sheet_get_max_cols (state->sheet) /* default is (2^14) */
1376 #define XLSX_MAX_ROWS gnm_sheet_get_max_rows (state->sheet) /* default is (2^20) */
1378 static void
1379 xlsx_write_sheet_view (GsfXMLOut *xml, SheetView const *sv)
1381 Sheet const *sheet = sv_sheet (sv);
1382 GnmColor *sheet_auto = sheet_style_get_auto_pattern_color (sheet);
1383 GnmColor *default_auto = style_color_auto_pattern ();
1384 GnmCellPos topLeft, frozen_topLeft;
1385 char const *activePane = NULL;
1386 int const frozen_height = sv->unfrozen_top_left.row -
1387 sv->frozen_top_left.row;
1388 int const frozen_width = sv->unfrozen_top_left.col -
1389 sv->frozen_top_left.col;
1390 int tmp;
1392 if (frozen_width > 0) {
1393 topLeft.col = sv->frozen_top_left.col;
1394 frozen_topLeft.col = sv->initial_top_left.col;
1395 } else {
1396 topLeft.col = sv->initial_top_left.col;
1397 frozen_topLeft.col = sv->frozen_top_left.col;
1399 if (frozen_height > 0) {
1400 topLeft.row = sv->frozen_top_left.row;
1401 frozen_topLeft.row = sv->initial_top_left.row;
1402 } else {
1403 topLeft.row = sv->initial_top_left.row;
1404 frozen_topLeft.row = sv->frozen_top_left.row;
1407 gsf_xml_out_start_element (xml, "sheetView");
1408 if (topLeft.col > 0 || topLeft.row > 0) /* A1 is the default */
1409 xlsx_add_pos (xml, "topLeftCell", &topLeft);
1410 gsf_xml_out_add_int (xml, "workbookViewId",
1411 wb_view_get_index_in_wb (sv_wbv (sv)));
1413 tmp = (int) (100.* sheet->last_zoom_factor_used + .5);
1414 if (tmp != 100)
1415 gsf_xml_out_add_int (xml, "zoomScale", tmp);
1417 switch (sv->view_mode) {
1418 case GNM_SHEET_VIEW_NORMAL_MODE : break;
1419 case GNM_SHEET_VIEW_PAGE_BREAK_MODE :
1420 gsf_xml_out_add_cstr_unchecked (xml, "view", "pageBreakPreview"); break;
1421 case GNM_SHEET_VIEW_LAYOUT_MODE :
1422 gsf_xml_out_add_cstr_unchecked (xml, "view", "pageLayout"); break;
1425 if (sheet->hide_grid)
1426 gsf_xml_out_add_cstr_unchecked (xml, "showGridLines", "0");
1427 if (sheet->display_formulas)
1428 gsf_xml_out_add_cstr_unchecked (xml, "showFormulas", "1");
1429 if (sheet->hide_col_header || sheet->hide_row_header)
1430 gsf_xml_out_add_cstr_unchecked (xml, "showRowColHeaders", "0");
1431 if (sheet->hide_zero)
1432 gsf_xml_out_add_cstr_unchecked (xml, "showZeros", "0");
1433 if (!sheet->display_outlines)
1434 gsf_xml_out_add_cstr_unchecked (xml, "showOutlineSymbols", "0");
1435 if (sheet->text_is_rtl)
1436 gsf_xml_out_add_cstr_unchecked (xml, "rightToLeft", "1");
1437 if (sheet == wb_view_cur_sheet (sv_wbv (sv)))
1438 gsf_xml_out_add_cstr_unchecked (xml, "tabSelected", "1");
1440 if (!style_color_equal (sheet_auto, default_auto)) {
1441 gsf_xml_out_add_cstr_unchecked (xml, "defaultGridColor", "1");
1442 #if 0
1443 gsf_xml_out_add_int (xml, "colorId", grid_color_index);
1444 #endif
1446 style_color_unref (sheet_auto);
1447 style_color_unref (default_auto);
1449 if (gnm_sheet_view_is_frozen (sv)) {
1450 activePane = "bottomRight"; /* h&v freeze */
1452 gsf_xml_out_start_element (xml, "pane");
1453 if (frozen_width > 0)
1454 gsf_xml_out_add_int (xml, "xSplit", frozen_width);
1455 else
1456 activePane = "bottomLeft"; /* v freeze */
1457 if (frozen_height > 0)
1458 gsf_xml_out_add_int (xml, "ySplit", frozen_height);
1459 else
1460 activePane = "topRight"; /* h freeze */
1461 xlsx_add_pos (xml, "topLeftCell", &frozen_topLeft);
1462 gsf_xml_out_add_cstr_unchecked (xml, "activePane", activePane);
1463 gsf_xml_out_add_cstr_unchecked (xml, "state", "frozen");
1464 gsf_xml_out_end_element (xml); /* </pane> */
1467 gsf_xml_out_start_element (xml, "selection");
1468 if (NULL != activePane)
1469 gsf_xml_out_add_cstr_unchecked (xml, "pane", activePane);
1470 /* activeCellId is always 0 for gnumeric */
1471 xlsx_add_pos (xml, "activeCell", &sv->edit_pos);
1472 xlsx_add_range_list (xml, "sqref", sv->selections);
1473 gsf_xml_out_end_element (xml); /* </selection> */
1475 gsf_xml_out_end_element (xml); /* </sheetView> */
1478 static void
1479 xlsx_write_init_row (gboolean *needs_row, GsfXMLOut *xml, int r, char const *span)
1481 if (*needs_row) {
1482 gsf_xml_out_start_element (xml, "row");
1483 gsf_xml_out_add_int (xml, "r", r+1);
1484 gsf_xml_out_add_cstr_unchecked (xml, "spans", span);
1485 *needs_row = FALSE;
1489 static gint
1490 xlsx_get_style_id (XLSXWriteState *state, GnmStyle const *style)
1492 gpointer tmp;
1494 g_return_val_if_fail (style != NULL, 0);
1496 if (NULL == (tmp = g_hash_table_lookup (state->styles_hash, style))) {
1497 g_ptr_array_add (state->styles_array, (gpointer) style);
1498 tmp = GINT_TO_POINTER (state->styles_array->len);
1499 gnm_style_ref (style);
1500 g_hash_table_insert (state->styles_hash, (gpointer) style, tmp);
1502 return GPOINTER_TO_INT (tmp) - 1;
1505 static gint
1506 xlsx_get_cond_style_id (XLSXWriteState *state, GnmStyle const *style)
1508 gpointer tmp;
1510 g_return_val_if_fail (style != NULL, 0);
1512 if (NULL == (tmp = g_hash_table_lookup (state->dxfs_hash, style))) {
1513 g_ptr_array_add (state->dxfs_array, (gpointer)style);
1514 tmp = GINT_TO_POINTER (state->dxfs_array->len);
1515 g_hash_table_insert (state->dxfs_hash, (gpointer)style, tmp);
1517 return GPOINTER_TO_INT (tmp) - 1;
1520 static gboolean
1521 row_boring (Sheet *sheet, int r)
1523 ColRowInfo const *ri = sheet_row_get (sheet, r);
1524 if (!ri)
1525 return TRUE;
1527 return (!ri->hard_size &&
1528 fabs (ri->size_pts - sheet->rows.default_style.size_pts) < 1e-6 &&
1529 !ri->is_collapsed &&
1530 ri->visible &&
1531 ri->outline_level == 0);
1534 static void
1535 xlsx_write_cells (XLSXWriteState *state, GsfXMLOut *xml,
1536 GnmRange const *extent, GnmStyle **col_styles)
1538 int r, c;
1539 char *content;
1540 GnmParsePos pp;
1541 GnmExprTop const *texpr;
1542 char *cheesy_span = g_strdup_printf ("%d:%d", extent->start.col+1, extent->end.col+1);
1543 Sheet *sheet = (Sheet *)state->sheet;
1544 GPtrArray *all_cells = sheet_cells (sheet, extent);
1545 guint cno = 0;
1546 int *boring_count;
1547 guint8 *non_defaults_rows = sheet_style_get_nondefault_rows (sheet, col_styles);
1549 boring_count = g_new0 (int, extent->end.row + 1);
1550 r = extent->end.row;
1551 boring_count[r] = row_boring (sheet, r);
1552 while (r-- > extent->start.row)
1553 boring_count[r] = row_boring (sheet, r)
1554 ? 1 + boring_count[r + 1]
1555 : 0;
1557 /* Add a NULL to simplify code. */
1558 g_ptr_array_add (all_cells, NULL);
1560 gsf_xml_out_start_element (xml, "sheetData");
1561 for (r = extent->start.row ; r <= extent->end.row ; r++) {
1562 gboolean needs_row = TRUE;
1564 if (boring_count[r] == 0) {
1565 ColRowInfo const *ri = sheet_row_get (sheet, r);
1567 /* The code here needs to match row_boring. */
1569 if (ri->hard_size) {
1570 xlsx_write_init_row (&needs_row, xml, r, cheesy_span);
1571 gsf_xml_out_add_cstr_unchecked (xml, "customHeight", "1");
1573 if (ri->hard_size || fabs (ri->size_pts - sheet->cols.default_style.size_pts) > 1e-6) {
1574 xlsx_write_init_row (&needs_row, xml, r, cheesy_span);
1575 go_xml_out_add_double (xml, "ht", ri->size_pts);
1577 if (ri->is_collapsed) {
1578 xlsx_write_init_row (&needs_row, xml, r, cheesy_span);
1579 gsf_xml_out_add_cstr_unchecked (xml, "collapsed", "1");
1581 if (!ri->visible) {
1582 xlsx_write_init_row (&needs_row, xml, r, cheesy_span);
1583 gsf_xml_out_add_cstr_unchecked (xml, "hidden", "1");
1585 if (ri->outline_level > 0) {
1586 xlsx_write_init_row (&needs_row, xml, r, cheesy_span);
1587 gsf_xml_out_add_int (xml, "outlineLevel", ri->outline_level);
1592 * If we didn't have to write anything yet and if the whole
1593 * row -- and possibly the ones after it -- are all
1594 * using default style, skip them.
1596 if (needs_row) {
1597 GnmCell *cell = g_ptr_array_index (all_cells, cno);
1598 int dr, rows = (cell ? cell->pos.row : extent->end.row + 1) - r;
1599 rows = MIN (rows, boring_count[r]);
1600 for (dr = 0; dr < rows; dr++)
1601 if (non_defaults_rows[r + dr])
1602 break;
1603 rows = MIN (rows, dr);
1604 if (rows > 0) {
1605 r += (rows - 1);
1606 continue;
1610 for (c = extent->start.col ; c <= extent->end.col ; c++) {
1611 GnmCell *cell;
1612 GnmValue const *val;
1613 GnmStyle const *style;
1614 GnmStyle *style1 = NULL;
1615 gint style_id;
1616 GOFormat const *fmt1, *fmt2;
1618 cell = g_ptr_array_index (all_cells, cno);
1619 if (cell && cell->pos.row == r && cell->pos.col == c) {
1620 cno++;
1621 val = cell->value;
1622 } else {
1623 cell = NULL;
1624 val = NULL;
1627 /* FIXME: Use sheet_style_get_row once per row */
1628 style = sheet_style_get (sheet, c, r);
1629 fmt1 = gnm_style_get_format (style);
1630 fmt2 = cell ? gnm_cell_get_format_given_style (cell, style) : fmt1;
1631 if (fmt1 != fmt2 && !go_format_is_markup (fmt2)) {
1632 style = style1 = gnm_style_dup (style);
1633 gnm_style_set_format (style1, fmt2);
1635 style_id = style && style != col_styles[c]
1636 ? xlsx_get_style_id (state, style)
1637 : -1;
1638 if (style1)
1639 gnm_style_unref (style1);
1641 if (cell) {
1642 char const *type;
1643 gboolean inlineStr = FALSE;
1644 int str_id = -1;
1646 xlsx_write_init_row (&needs_row, xml, r, cheesy_span);
1647 gsf_xml_out_start_element (xml, "c");
1648 gsf_xml_out_add_cstr_unchecked (xml, "r",
1649 cell_coord_name (c, r));
1650 if (style_id > -1)
1651 gsf_xml_out_add_int (xml, "s", style_id);
1653 switch (val->v_any.type) {
1654 default :
1655 case VALUE_EMPTY:
1656 type = NULL; /* FIXME : what to do ? */
1657 break;
1658 case VALUE_BOOLEAN:
1659 type = "b";
1660 break;
1661 case VALUE_FLOAT:
1662 type = ""; /* "n" is the default */
1663 break;
1664 case VALUE_ERROR:
1665 type = "e";
1666 break;
1667 case VALUE_STRING:
1668 /* A reasonable approximation of * 'is_shared'. It can get spoofed by
1669 * rich text references to a base * string */
1670 if (go_string_get_ref_count (val->v_str.val) > 1) {
1671 str_id = xlsx_shared_string (state, val);
1672 type = "s";
1673 } else if (gnm_cell_has_expr (cell))
1674 type = "str";
1675 else {
1676 type = "inlineStr";
1677 inlineStr = TRUE;
1679 break;
1680 case VALUE_CELLRANGE:
1681 case VALUE_ARRAY:
1682 type = NULL; /* FIXME */
1683 break;
1686 if (NULL != type && *type)
1687 gsf_xml_out_add_cstr_unchecked (xml, "t", type);
1689 if (gnm_cell_has_expr (cell)) {
1690 texpr = cell->base.texpr;
1691 if (!gnm_expr_top_is_array_elem (texpr, NULL, NULL)) {
1692 gsf_xml_out_start_element (xml, "f");
1694 if (gnm_expr_top_is_array_corner (texpr)) {
1695 GnmRange r;
1696 int cols, rows;
1698 gnm_expr_top_get_array_size (texpr, &cols, &rows);
1699 range_init_cellpos_size (&r, &cell->pos,
1700 cols, rows);
1701 gsf_xml_out_add_cstr_unchecked (xml, "t", "array");
1702 xlsx_add_range (xml, "ref", &r);
1704 content = gnm_expr_top_as_string (cell->base.texpr,
1705 parse_pos_init_cell (&pp, cell), state->convs);
1706 gsf_xml_out_add_cstr (xml, NULL, content);
1707 g_free (content);
1709 gsf_xml_out_end_element (xml); /* </f> */
1713 if (inlineStr) {
1714 GOFormat const *fmt = VALUE_FMT (val);
1715 PangoAttrList *attrs =
1716 fmt && go_format_is_markup (fmt)
1717 ? (PangoAttrList *)go_format_get_markup (VALUE_FMT (val))
1718 : NULL;
1720 gsf_xml_out_start_element (xml, "is");
1721 xlsx_write_rich_text (xml,
1722 value_peek_string (val),
1723 attrs,
1724 FALSE);
1725 gsf_xml_out_end_element (xml); /* </is> */
1726 } else if (type) {
1727 gsf_xml_out_start_element (xml, "v");
1728 if (str_id >= 0)
1729 gsf_xml_out_add_int (xml, NULL, str_id);
1730 else if (VALUE_IS_BOOLEAN (val))
1731 xlsx_add_bool (xml, NULL, value_get_as_int (val));
1732 else {
1733 GString *str = g_string_new (NULL);
1734 value_get_as_gstring (cell->value, str, state->convs);
1735 gsf_xml_out_add_cstr (xml, NULL, str->str);
1736 g_string_free (str, TRUE);
1738 gsf_xml_out_end_element (xml); /* </v> */
1741 gsf_xml_out_end_element (xml); /* </c> */
1742 } else if (style_id > -1) {
1743 xlsx_write_init_row (&needs_row, xml, r, cheesy_span);
1744 gsf_xml_out_start_element (xml, "c");
1745 gsf_xml_out_add_cstr_unchecked (xml, "r",
1746 cell_coord_name (c, r));
1747 gsf_xml_out_add_int (xml, "s", style_id);
1748 gsf_xml_out_end_element (xml); /* </c> */
1751 if (!needs_row)
1752 gsf_xml_out_end_element (xml); /* </row> */
1754 gsf_xml_out_end_element (xml); /* </sheetData> */
1755 g_free (non_defaults_rows);
1756 g_free (boring_count);
1757 g_ptr_array_free (all_cells, TRUE);
1758 g_free (cheesy_span);
1761 static void
1762 xlsx_write_merges (XLSXWriteState *state, GsfXMLOut *xml)
1764 GSList *ptr;
1766 if (NULL != (ptr = state->sheet->list_merged)) {
1767 gsf_xml_out_start_element (xml, "mergeCells");
1768 for (; ptr != NULL ; ptr = ptr->next) {
1769 gsf_xml_out_start_element (xml, "mergeCell");
1770 xlsx_add_range (xml, "ref", ptr->data);
1771 gsf_xml_out_end_element (xml); /* </mergeCell> */
1773 gsf_xml_out_end_element (xml); /* </mergeCells> */
1777 static void
1778 xlsx_write_cond_rule (XLSXWriteState *state, GsfXMLOut *xml,
1779 GnmStyleCond *cond, GnmParsePos const *pp)
1781 int i, n;
1782 const char *type = NULL;
1783 const char *operator = NULL;
1784 GnmExprTop const *alt_texpr;
1785 gboolean needs_alt_texpr = FALSE;
1787 gsf_xml_out_start_element (xml, "cfRule");
1788 switch (cond->op) {
1789 case GNM_STYLE_COND_BETWEEN:
1790 n = 2; operator = "between";
1791 break;
1792 case GNM_STYLE_COND_NOT_BETWEEN:
1793 n = 2; operator = "notBetween";
1794 break;
1795 case GNM_STYLE_COND_EQUAL:
1796 n = 1; operator = "equal";
1797 break;
1798 case GNM_STYLE_COND_NOT_EQUAL:
1799 n = 1; operator = "notEqual";
1800 break;
1801 case GNM_STYLE_COND_GT:
1802 n = 1; operator = "greaterThan";
1803 break;
1804 case GNM_STYLE_COND_LT:
1805 n = 1; operator = "lessThan";
1806 break;
1807 case GNM_STYLE_COND_GTE:
1808 n = 1; operator = "greaterThanOrEqual";
1809 break;
1810 case GNM_STYLE_COND_LTE:
1811 n = 1; operator = "lessThanOrEqual";
1812 break;
1813 case GNM_STYLE_COND_CONTAINS_BLANKS:
1814 needs_alt_texpr = TRUE;
1815 n = 1; type = "containsBlanks";
1816 break;
1817 case GNM_STYLE_COND_NOT_CONTAINS_BLANKS:
1818 needs_alt_texpr = TRUE;
1819 n = 1; type = "notContainsBlanks";
1820 break;
1821 case GNM_STYLE_COND_CONTAINS_ERR:
1822 needs_alt_texpr = TRUE;
1823 n = 1; type = "containsErrors";
1824 break;
1825 case GNM_STYLE_COND_NOT_CONTAINS_ERR:
1826 needs_alt_texpr = TRUE;
1827 n = 1; type = "notContainsErrors";
1828 break;
1829 case GNM_STYLE_COND_CUSTOM:
1830 n = 1; type = "expression";
1831 break;
1832 case GNM_STYLE_COND_CONTAINS_STR:
1833 needs_alt_texpr = TRUE;
1834 n = 1; type = "containsText";
1835 break;
1836 case GNM_STYLE_COND_NOT_CONTAINS_STR:
1837 needs_alt_texpr = TRUE;
1838 n = 1; type = "notContainsText";
1839 break;
1840 case GNM_STYLE_COND_BEGINS_WITH_STR:
1841 needs_alt_texpr = TRUE;
1842 n = 1; type = "beginsWith";
1843 break;
1844 case GNM_STYLE_COND_ENDS_WITH_STR:
1845 needs_alt_texpr = TRUE;
1846 n = 1; type = "endsWith";
1847 break;
1849 case GNM_STYLE_COND_NOT_BEGINS_WITH_STR:
1850 case GNM_STYLE_COND_NOT_ENDS_WITH_STR:
1851 needs_alt_texpr = TRUE;
1852 n = 1; type = "expression";
1853 break;
1855 default:
1856 g_assert_not_reached ();
1859 alt_texpr = needs_alt_texpr
1860 ? gnm_style_cond_get_alternate_expr (cond)
1861 : NULL;
1863 gsf_xml_out_add_cstr_unchecked (xml, "type", type ? type : "cellIs");
1864 gsf_xml_out_add_int (xml, "dxfId",
1865 xlsx_get_cond_style_id (state, cond->overlay));
1866 gsf_xml_out_add_int (xml, "priority", 1) ;
1867 gsf_xml_out_add_int (xml, "stopIfTrue", 1) ;
1868 if (operator)
1869 gsf_xml_out_add_cstr_unchecked (xml, "operator", operator);
1870 for (i = 0; i < n; i++) {
1871 GnmExprTop const *texpr = alt_texpr
1872 ? alt_texpr
1873 : gnm_style_cond_get_expr (cond, i);
1874 char *str = gnm_expr_top_as_string (texpr, pp, state->convs);
1875 gsf_xml_out_simple_element (xml, "formula", str);
1876 g_free (str);
1878 if (alt_texpr)
1879 gnm_expr_top_unref (alt_texpr);
1880 gsf_xml_out_end_element (xml); /* </cfRule> */
1884 static void
1885 xlsx_write_conditional_formatting (XLSXWriteState *state, GsfXMLOut *xml)
1887 GnmStyleList *cond_styles =
1888 sheet_style_collect_conditions (state->sheet, NULL);
1889 GnmStyleList *l;
1890 if (!cond_styles)
1891 return;
1893 for (l = cond_styles; l; l = l->next) {
1894 GnmStyleRegion const *sr = l->data;
1895 GnmStyleConditions *sc = gnm_style_get_conditions (sr->style);
1896 GPtrArray const *conds = gnm_style_conditions_details (sc);
1897 unsigned ui;
1898 GnmParsePos pp;
1900 if (!conds)
1901 continue;
1903 parse_pos_init (&pp, NULL, state->sheet,
1904 sr->range.start.col, sr->range.start.row);
1905 gsf_xml_out_start_element (xml, "conditionalFormatting");
1906 xlsx_add_range (xml, "sqref", &sr->range);
1907 for (ui = 0; ui < conds->len; ui++) {
1908 GnmStyleCond *cond = g_ptr_array_index (conds, ui);
1909 xlsx_write_cond_rule (state, xml, cond, &pp);
1911 gsf_xml_out_end_element (xml); /* </conditionalFormatting> */
1914 style_list_free (cond_styles);
1918 static void
1919 xlsx_write_validation_expr (XLSXClosure *info, GnmCellPos const *pos,
1920 char const *elem, GnmExprTop const *texpr)
1922 if (NULL != texpr) {
1923 GnmParsePos pp;
1924 char *str = gnm_expr_top_as_string (texpr,
1925 parse_pos_init (&pp, NULL, (Sheet *)info->state->sheet,
1926 pos->col, pos->row),
1927 info->state->convs);
1928 gsf_xml_out_simple_element (info->xml, elem, str);
1929 g_free (str);
1933 static void
1934 xlsx_write_validation (XLValInputPair const *vip, G_GNUC_UNUSED gpointer dummy, XLSXClosure *info)
1936 #if 0
1937 /* Get docs on this */
1938 "imeMode" default="noControl"
1939 "noControl"
1940 "off"
1941 "on"
1942 "disabled"
1943 "hiragana"
1944 "fullKatakana"
1945 "halfKatakana"
1946 "fullAlpha"
1947 "halfAlpha"
1948 "fullHangul"
1949 "halfHangul"
1950 #endif
1951 char const *tmp;
1953 gsf_xml_out_start_element (info->xml, "dataValidation");
1955 if (NULL != vip->v) {
1956 tmp = NULL;
1957 switch (vip->v->type) {
1958 default : /* fall back to the default */
1959 case GNM_VALIDATION_TYPE_ANY : /* the default "none" */ break;
1960 case GNM_VALIDATION_TYPE_AS_INT : tmp = "whole"; break;
1961 case GNM_VALIDATION_TYPE_AS_NUMBER : tmp = "decimal"; break;
1962 case GNM_VALIDATION_TYPE_IN_LIST : tmp = "list"; break;
1963 case GNM_VALIDATION_TYPE_AS_DATE : tmp = "date"; break;
1964 case GNM_VALIDATION_TYPE_AS_TIME : tmp = "time"; break;
1965 case GNM_VALIDATION_TYPE_TEXT_LENGTH : tmp = "textLength"; break;
1966 case GNM_VALIDATION_TYPE_CUSTOM : tmp = "custom"; break;
1968 if (NULL != tmp)
1969 gsf_xml_out_add_cstr_unchecked (info->xml, "type", tmp);
1971 tmp = NULL;
1972 switch (vip->v->op) {
1973 default : /* fall back to the default */
1974 case GNM_VALIDATION_OP_BETWEEN : /* the default "between" */ break;
1975 case GNM_VALIDATION_OP_NOT_BETWEEN: tmp = "notBetween"; break;
1976 case GNM_VALIDATION_OP_EQUAL : tmp = "equal"; break;
1977 case GNM_VALIDATION_OP_NOT_EQUAL : tmp = "notEqual"; break;
1978 case GNM_VALIDATION_OP_LT : tmp = "lessThan"; break;
1979 case GNM_VALIDATION_OP_GT : tmp = "greaterThan"; break;
1980 case GNM_VALIDATION_OP_LTE : tmp = "lessThanOrEqual"; break;
1981 case GNM_VALIDATION_OP_GTE : tmp = "greaterThanOrEqual"; break;
1983 if (NULL != tmp)
1984 gsf_xml_out_add_cstr_unchecked (info->xml, "operator", tmp);
1986 tmp = NULL;
1987 switch (vip->v->style) {
1988 default : /* fall back to the default */
1989 case GNM_VALIDATION_STYLE_STOP : /* "stop" the default */ break;
1990 case GNM_VALIDATION_STYLE_WARNING : tmp = "warning"; break;
1991 case GNM_VALIDATION_STYLE_INFO : tmp = "information"; break;
1993 if (NULL != tmp)
1994 gsf_xml_out_add_cstr_unchecked (info->xml, "errorStyle", tmp);
1996 if (vip->v->allow_blank)
1997 xlsx_add_bool (info->xml, "allowBlank", TRUE);
1998 if (vip->v->use_dropdown)
1999 xlsx_add_bool (info->xml, "showDropDown", TRUE);
2001 if (NULL != vip->v->title)
2002 gsf_xml_out_add_cstr (info->xml, "errorTitle", vip->v->title->str);
2003 if (NULL != vip->v->msg)
2004 gsf_xml_out_add_cstr (info->xml, "error", vip->v->msg->str);
2007 /* ?? Always TRUE but not the default ?? */
2008 xlsx_add_bool (info->xml, "showInputMessage", TRUE);
2009 xlsx_add_bool (info->xml, "showErrorMessage", TRUE);
2011 if (NULL != vip->msg) {
2012 char const *str;
2013 if (NULL != (str = gnm_input_msg_get_title (vip->msg)))
2014 gsf_xml_out_add_cstr (info->xml, "promptTitle", str);
2015 if (NULL != (str = gnm_input_msg_get_msg (vip->msg)))
2016 gsf_xml_out_add_cstr (info->xml, "prompt", str);
2019 xlsx_add_range_list (info->xml, "sqref", vip->ranges);
2021 if (NULL != vip->v) {
2022 GnmRange const *first = vip->ranges->data;
2023 xlsx_write_validation_expr (info, &first->start,
2024 "formula1", vip->v->deps[0].texpr);
2025 xlsx_write_validation_expr (info, &first->start,
2026 "formula2", vip->v->deps[1].texpr);
2029 gsf_xml_out_end_element (info->xml); /* </dataValidation> */
2032 static int
2033 by_first_range (gpointer vip_a, G_GNUC_UNUSED gpointer val_a,
2034 gpointer vip_b, G_GNUC_UNUSED gpointer val_b,
2035 G_GNUC_UNUSED gpointer user)
2037 const XLValInputPair *a = vip_a;
2038 const XLValInputPair *b = vip_b;
2039 return gnm_range_compare (a->ranges->data, b->ranges->data);
2042 static void
2043 xlsx_write_validations (XLSXWriteState *state, GsfXMLOut *xml, G_GNUC_UNUSED GnmRange const *extent)
2045 GnmStyleList *validations = sheet_style_collect_validations (state->sheet, NULL);
2047 if (NULL != validations) {
2048 XLSXClosure info = { state, xml };
2049 /* filter on logical max, not extent. XL allows validations
2050 * past the stated dimension */
2051 GHashTable *group = xls_collect_validations (validations,
2052 XLSX_MAX_COLS, XLSX_MAX_ROWS);
2054 gsf_xml_out_start_element (xml, "dataValidations");
2055 gsf_xml_out_add_int (xml, "count", g_hash_table_size (group)) ;
2056 gnm_hash_table_foreach_ordered
2057 (group, (GHFunc)xlsx_write_validation,
2058 by_first_range,
2059 &info);
2060 gsf_xml_out_end_element (xml); /* </dataValidations> */
2062 g_hash_table_destroy (group);
2063 style_list_free (validations);
2067 static void
2068 xlsx_write_hlink (GnmHLink const *lnk, GSList *ranges, XLSXClosure *info)
2070 gchar *target = g_strdup (gnm_hlink_get_target (lnk));
2071 gchar const *rid = NULL;
2072 gchar *location = NULL;
2073 gchar const *tip = gnm_hlink_get_tip (lnk);
2074 GType const t = G_OBJECT_TYPE (lnk);
2076 if (target && g_type_is_a (t, gnm_hlink_url_get_type ())) {
2077 // URLs, including email.
2079 char *hash = strchr (target, '#');
2080 if (hash) {
2081 location = g_strdup (hash + 1);
2082 *hash = 0;
2085 rid = gsf_outfile_open_pkg_add_extern_rel (
2086 GSF_OUTFILE_OPEN_PKG (gsf_xml_out_get_output (info->xml)),
2087 target, ns_rel_hlink);
2088 } else if (t == gnm_hlink_cur_wb_get_type ()) {
2089 location = target;
2090 target = NULL;
2091 } else {
2092 g_free (target);
2093 g_free (location);
2094 return;
2097 for (; ranges != NULL ; ranges = ranges->next) {
2098 GnmRange const *range = ranges->data;
2100 gsf_xml_out_start_element (info->xml, "hyperlink");
2101 xlsx_add_range (info->xml, "ref", range);
2103 if (rid)
2104 gsf_xml_out_add_cstr (info->xml, "r:id", rid);
2105 if (location)
2106 gsf_xml_out_add_cstr (info->xml, "location", location);
2107 if (tip)
2108 gsf_xml_out_add_cstr (info->xml, "tooltip", tip);
2110 gsf_xml_out_end_element (info->xml); /* </hyperlink> */
2113 g_free (target);
2114 g_free (location);
2117 static int
2118 by_hlink_order (G_GNUC_UNUSED gpointer link_a, gpointer val_a,
2119 G_GNUC_UNUSED gpointer link_b, gpointer val_b,
2120 G_GNUC_UNUSED gpointer user)
2122 GList *ranges_a = val_a;
2123 GList *ranges_b = val_b;
2125 return gnm_range_compare (ranges_a->data, ranges_b->data);
2128 static void
2129 xlsx_write_hlinks (XLSXWriteState *state, GsfXMLOut *xml, G_GNUC_UNUSED GnmRange const *extent)
2131 GnmStyleList *hlinks = sheet_style_collect_hlinks (state->sheet, NULL);
2133 if (NULL != hlinks) {
2134 XLSXClosure info = { state, xml };
2135 /* filter on logical max, not extent. XL allows validations
2136 * past the stated dimension */
2137 GHashTable *group = xls_collect_hlinks (hlinks,
2138 XLSX_MAX_COLS, XLSX_MAX_ROWS);
2140 gsf_xml_out_start_element (xml, "hyperlinks");
2141 gnm_hash_table_foreach_ordered
2142 (group, (GHFunc) xlsx_write_hlink,
2143 by_hlink_order,
2144 &info);
2145 gsf_xml_out_end_element (xml); /* </hyperlinks> */
2147 g_hash_table_destroy (group);
2148 style_list_free (hlinks);
2152 static void
2153 xlsx_write_col (XLSXWriteState *state, GsfXMLOut *xml,
2154 ColRowInfo const *ci, int first, int last,
2155 GnmStyle *style)
2157 double const def_width = state->sheet->cols.default_style.size_pts;
2158 gint style_id = xlsx_get_style_id (state, style);
2160 gsf_xml_out_start_element (xml, "col");
2161 gsf_xml_out_add_int (xml, "min", first + 1);
2162 gsf_xml_out_add_int (xml, "max", last + 1);
2163 gsf_xml_out_add_int (xml, "style", style_id);
2165 go_xml_out_add_double (xml, "width",
2166 (ci ? ci->size_pts : def_width) /
2167 ((130. / 18.5703125) * (72./96.)));
2169 if (ci) {
2170 if (!ci->visible)
2171 gsf_xml_out_add_cstr_unchecked (xml, "hidden", "1");
2172 if (ci->hard_size)
2173 gsf_xml_out_add_cstr_unchecked (xml, "customWidth", "1");
2174 else if (fabs (def_width - ci->size_pts) > .1) {
2175 gsf_xml_out_add_cstr_unchecked (xml, "bestFit", "1");
2176 gsf_xml_out_add_cstr_unchecked (xml, "customWidth", "1");
2179 if (ci->outline_level > 0)
2180 gsf_xml_out_add_int (xml, "outlineLevel",
2181 ci->outline_level);
2182 if (ci->is_collapsed)
2183 gsf_xml_out_add_cstr_unchecked (xml, "collapsed", "1");
2186 gsf_xml_out_end_element (xml); /* </col> */
2189 static void
2190 xlsx_write_cols (XLSXWriteState *state, GsfXMLOut *xml, GnmStyle **styles)
2192 int first_col = 0, i;
2193 int last_col = gnm_sheet_get_last_col (state->sheet);
2194 ColRowInfo const *info = sheet_col_get (state->sheet, first_col);
2196 gsf_xml_out_start_element (xml, "cols");
2198 for (i = first_col + 1; i <= last_col; i++) {
2199 ColRowInfo const *ci = sheet_col_get_info (state->sheet, i);
2200 if (!col_row_info_equal (info, ci) || styles[i] != styles[i - 1]) {
2201 xlsx_write_col (state, xml, info,
2202 first_col, i - 1,
2203 styles[i - 1]);
2204 info = ci;
2205 first_col = i;
2208 xlsx_write_col (state, xml, info,
2209 first_col, i - 1,
2210 styles[i - 1]);
2212 gsf_xml_out_end_element (xml); /* </cols> */
2215 static void
2216 xlsx_write_autofilters (XLSXWriteState *state, GsfXMLOut *xml)
2218 GnmFilter const *filter;
2219 unsigned i;
2221 if (NULL == state->sheet->filters)
2222 return;
2224 /* We write only the first filter per sheet. */
2225 filter = state->sheet->filters->data;
2227 gsf_xml_out_start_element (xml, "autoFilter");
2228 xlsx_add_range (xml, "ref", &filter->r);
2230 for (i = 0; i < filter->fields->len ; i++) {
2231 GnmFilterCondition const *cond =
2232 gnm_filter_get_condition (filter, i);
2234 /* filter unused or bucket filters in excel5 */
2235 if (!cond || cond->op[0] == GNM_FILTER_UNUSED)
2236 continue;
2238 gsf_xml_out_start_element (xml, "filterColumn");
2239 gsf_xml_out_add_int (xml, "colId", i);
2241 switch (cond->op[0]) {
2242 case GNM_FILTER_OP_EQUAL:
2243 case GNM_FILTER_OP_GT:
2244 case GNM_FILTER_OP_LT:
2245 case GNM_FILTER_OP_GTE:
2246 case GNM_FILTER_OP_LTE:
2247 case GNM_FILTER_OP_NOT_EQUAL: {
2248 static const char *const opname[6] = {
2249 "equal", "greaterThan", "lessThan",
2250 "greaterThanOrEqual", "lessThanOrEqual",
2251 "notEqual"
2253 unsigned oi, N;
2255 for (oi = N = 1; oi < G_N_ELEMENTS (cond->op); oi++) {
2256 if (cond->op[oi] == GNM_FILTER_UNUSED)
2257 continue;
2258 N++;
2261 gsf_xml_out_start_element (xml, "customFilters");
2262 if (N > 1)
2263 gsf_xml_out_add_cstr_unchecked (xml, "and", "true");
2264 for (oi = 0; oi < G_N_ELEMENTS (cond->op); oi++) {
2265 GnmFilterOp op = cond->op[oi];
2266 GString *str;
2268 if (op == GNM_FILTER_UNUSED)
2269 continue;
2270 gsf_xml_out_start_element (xml, "customFilter");
2271 if (op >= GNM_FILTER_OP_EQUAL && op <= GNM_FILTER_OP_NOT_EQUAL)
2272 gsf_xml_out_add_cstr_unchecked (xml, "operator", opname[op]);
2274 str = g_string_new (NULL);
2275 value_get_as_gstring (cond->value[oi], str, state->convs);
2276 gsf_xml_out_add_cstr (xml, "val", str->str);
2277 g_string_free (str, TRUE);
2278 gsf_xml_out_end_element (xml); /* </customFilter> */
2280 gsf_xml_out_end_element (xml); /* </customFilters> */
2281 break;
2284 case GNM_FILTER_OP_BLANKS :
2285 gsf_xml_out_start_element (xml, "filters");
2286 xlsx_add_bool (xml, "blank", TRUE);
2287 gsf_xml_out_end_element (xml); /* </filters> */
2288 break;
2289 case GNM_FILTER_OP_NON_BLANKS :
2290 gsf_xml_out_start_element (xml, "customFilters");
2291 gsf_xml_out_start_element (xml, "customFilter");
2292 gsf_xml_out_add_cstr_unchecked (xml, "operator", "notEqual");
2293 gsf_xml_out_add_cstr (xml, "val", " ");
2294 gsf_xml_out_end_element (xml); /* </customFilter> */
2295 gsf_xml_out_end_element (xml); /* </customFilters> */
2296 break;
2298 case GNM_FILTER_OP_TOP_N:
2299 case GNM_FILTER_OP_BOTTOM_N:
2300 case GNM_FILTER_OP_TOP_N_PERCENT:
2301 case GNM_FILTER_OP_BOTTOM_N_PERCENT:
2302 gsf_xml_out_start_element (xml, "top10");
2303 go_xml_out_add_double (xml, "val", cond->count);
2304 if (cond->op[0] & GNM_FILTER_OP_BOTTOM_MASK)
2305 gsf_xml_out_add_cstr_unchecked (xml, "top", "0");
2306 if (cond->op[0] & GNM_FILTER_OP_PERCENT_MASK)
2307 gsf_xml_out_add_cstr_unchecked (xml, "percent", "1");
2308 gsf_xml_out_end_element (xml); /* </top10> */
2309 break;
2311 default:
2312 continue;
2315 gsf_xml_out_end_element (xml); /* </filterColumn> */
2317 gsf_xml_out_end_element (xml); /* </autoFilter> */
2320 static void
2321 xlsx_write_protection (XLSXWriteState *state, GsfXMLOut *xml)
2323 gboolean sheet;
2324 gboolean objects;
2325 gboolean scenarios;
2326 gboolean formatCells;
2327 gboolean formatColumns;
2328 gboolean formatRows;
2329 gboolean insertColumns;
2330 gboolean insertRows;
2331 gboolean insertHyperlinks;
2332 gboolean deleteColumns;
2333 gboolean deleteRows;
2334 gboolean selectLockedCells;
2335 gboolean sort;
2336 gboolean autoFilter;
2337 gboolean pivotTables;
2338 gboolean selectUnlockedCells;
2340 g_object_get (G_OBJECT (state->sheet),
2341 "protected", &sheet,
2342 "protected-allow-edit-objects", &objects,
2343 "protected-allow-edit-scenarios", &scenarios,
2344 "protected-allow-cell-formatting", &formatCells,
2345 "protected-allow-column-formatting", &formatColumns,
2346 "protected-allow-row-formatting", &formatRows,
2347 "protected-allow-insert-columns", &insertColumns,
2348 "protected-allow-insert-rows", &insertRows,
2349 "protected-allow-insert-hyperlinks", &insertHyperlinks,
2350 "protected-allow-delete-columns", &deleteColumns,
2351 "protected-allow-delete-rows", &deleteRows,
2352 "protected-allow-select-locked-cells", &selectLockedCells,
2353 "protected-allow-sort-ranges", &sort,
2354 "protected-allow-edit-auto-filters", &autoFilter,
2355 "protected-allow-edit-pivottable", &pivotTables,
2356 "protected-allow-select-unlocked-cells", &selectUnlockedCells,
2357 NULL);
2359 gsf_xml_out_start_element (xml, "sheetProtection");
2360 if ( sheet) xlsx_add_bool (xml, "sheet", TRUE);
2361 if ( objects) xlsx_add_bool (xml, "objects", TRUE);
2362 if ( scenarios) xlsx_add_bool (xml, "scenarios", TRUE);
2363 if (!formatCells) xlsx_add_bool (xml, "formatCells", FALSE);
2364 if (!formatColumns) xlsx_add_bool (xml, "formatColumns", FALSE);
2365 if (!formatRows) xlsx_add_bool (xml, "formatRows", FALSE);
2366 if (!insertColumns) xlsx_add_bool (xml, "insertColumns", FALSE);
2367 if (!insertRows) xlsx_add_bool (xml, "insertRows", FALSE);
2368 if (!insertHyperlinks) xlsx_add_bool (xml, "insertHyperlinks", FALSE);
2369 if (!deleteColumns) xlsx_add_bool (xml, "deleteColumns", FALSE);
2370 if (!deleteRows) xlsx_add_bool (xml, "deleteRows", FALSE);
2371 if ( selectLockedCells) xlsx_add_bool (xml, "selectLockedCells", TRUE);
2372 if (!sort) xlsx_add_bool (xml, "sort", FALSE);
2373 if (!autoFilter) xlsx_add_bool (xml, "autoFilter", FALSE);
2374 if (!pivotTables) xlsx_add_bool (xml, "pivotTables", FALSE);
2375 if ( selectUnlockedCells) xlsx_add_bool (xml, "selectUnlockedCells", TRUE);
2377 gsf_xml_out_end_element (xml); /* sheetProtection */
2380 static void
2381 xlsx_write_breaks (G_GNUC_UNUSED XLSXWriteState *state, GsfXMLOut *xml, GnmPageBreaks *breaks)
2383 unsigned const maxima = (breaks->is_vert ? XLSX_MaxCol : XLSX_MaxRow) - 1;
2384 GArray const *details = breaks->details;
2385 GnmPageBreak const *binfo;
2386 unsigned i;
2388 gsf_xml_out_start_element (xml,
2389 (breaks->is_vert) ? "rowBreaks" : "colBreaks");
2390 gsf_xml_out_add_int (xml, "count", details->len);
2392 for (i = 0 ; i < details->len ; i++) {
2393 binfo = &g_array_index (details, GnmPageBreak, i);
2394 gsf_xml_out_start_element (xml, "brk");
2395 gsf_xml_out_add_int (xml, "id", binfo->pos);
2397 /* hard code min=0 max=dir */
2398 gsf_xml_out_add_int (xml, "max", maxima);
2400 switch (binfo->type) {
2401 case GNM_PAGE_BREAK_MANUAL : gsf_xml_out_add_bool (xml, "man", TRUE); break;
2402 case GNM_PAGE_BREAK_AUTO : break;
2403 case GNM_PAGE_BREAK_NONE : break;
2404 case GNM_PAGE_BREAK_DATA_SLICE :gsf_xml_out_add_bool (xml, "pt", TRUE); break;
2406 gsf_xml_out_end_element (xml); /* </brk> */
2408 gsf_xml_out_end_element (xml);
2411 static int
2412 xlsx_find_paper_code (GtkPaperSize *psize)
2414 XLSXPaperDefs *paper_defs;
2415 XLSXPaperDefs paper[] =
2416 {{ 74 , 90 , 90 , 205 , GTK_UNIT_MM },
2417 { 38 , 92 , 3.625 , 6.5 , GTK_UNIT_INCH },
2418 { 94 , 97 , 97 , 151 , GTK_UNIT_MM },
2419 /* { 95 , 97 , 97 , 151 , GTK_UNIT_MM }, */
2420 { 37 , 98 , 3.875 , 7.5 , GTK_UNIT_INCH },
2421 { 19 , 98 , 3.875 , 8.875 , GTK_UNIT_INCH },
2422 { 96 , 102 , 102 , 165 , GTK_UNIT_MM },
2423 { 97 , 102 , 102 , 176 , GTK_UNIT_MM },
2424 { 20 , 104 , 4.125 , 9.5 , GTK_UNIT_INCH },
2425 { 70 , 105 , 105 , 148 , GTK_UNIT_MM },
2426 { 92 , 105 , 105 , 235 , GTK_UNIT_MM },
2427 { 99 , 110 , 110 , 208 , GTK_UNIT_MM },
2428 { 27 , 110 , 110 , 220 , GTK_UNIT_MM },
2429 /* { 100 , 110 , 110 , 220 , GTK_UNIT_MM }, */
2430 { 36 , 110 , 110 , 230 , GTK_UNIT_MM },
2431 { 21 , 114 , 4.5 , 10.375 , GTK_UNIT_INCH },
2432 { 31 , 114 , 114 , 162 , GTK_UNIT_MM },
2433 { 32 , 114 , 114 , 229 , GTK_UNIT_MM },
2434 { 22 , 120 , 4.75 , 11 , GTK_UNIT_INCH },
2435 { 101 , 120 , 120 , 230 , GTK_UNIT_MM },
2436 { 73 , 120 , 120 , 235 , GTK_UNIT_MM },
2437 { 103 , 120 , 120 , 309 , GTK_UNIT_MM },
2438 { 98 , 125 , 125 , 176 , GTK_UNIT_MM },
2439 { 23 , 127 , 5 , 11.5 , GTK_UNIT_INCH },
2440 { 88 , 128 , 128 , 182 , GTK_UNIT_MM },
2441 { 6 , 139 , 5.5 , 8.5 , GTK_UNIT_INCH },
2442 { 93 , 146 , 146 , 215 , GTK_UNIT_MM },
2443 { 81 , 148 , 148 , 100 , GTK_UNIT_MM },
2444 { 83 , 148 , 148 , 105 , GTK_UNIT_MM },
2445 { 82 , 148 , 148 , 200 , GTK_UNIT_MM },
2446 { 11 , 148 , 148 , 210 , GTK_UNIT_MM },
2447 /* { 61 , 148 , 148 , 210 , GTK_UNIT_MM }, */
2448 { 107 , 151 , 151 , 97 , GTK_UNIT_MM },
2449 /* { 108 , 151 , 151 , 97 , GTK_UNIT_MM }, */
2450 { 102 , 160 , 160 , 230 , GTK_UNIT_MM },
2451 { 28 , 162 , 162 , 229 , GTK_UNIT_MM },
2452 { 109 , 165 , 165 , 102 , GTK_UNIT_MM },
2453 { 64 , 174 , 174 , 235 , GTK_UNIT_MM },
2454 { 110 , 176 , 176 , 102 , GTK_UNIT_MM },
2455 { 35 , 176 , 176 , 125 , GTK_UNIT_MM },
2456 /* { 111 , 176 , 176 , 125 , GTK_UNIT_MM }, */
2457 { 13 , 176 , 176 , 250 , GTK_UNIT_MM },
2458 /* { 34 , 176 , 176 , 250 , GTK_UNIT_MM }, */
2459 { 89 , 182 , 182 , 128 , GTK_UNIT_MM },
2460 { 62 , 182 , 182 , 257 , GTK_UNIT_MM },
2461 { 7 , 184 , 7.25 , 10.5 , GTK_UNIT_INCH },
2462 { 43 , 200 , 200 , 148 , GTK_UNIT_MM },
2463 /* { 69 , 200 , 200 , 148 , GTK_UNIT_MM }, */
2464 { 65 , 201 , 201 , 276 , GTK_UNIT_MM },
2465 { 87 , 205 , 205 , 90 , GTK_UNIT_MM },
2466 { 112 , 208 , 208 , 110 , GTK_UNIT_MM },
2467 { 54 , 210 , 8.275 , 11 , GTK_UNIT_INCH },
2468 { 78 , 210 , 210 , 148 , GTK_UNIT_MM },
2469 { 9 , 210 , 210 , 297 , GTK_UNIT_MM },
2470 /* { 10 , 210 , 210 , 297 , GTK_UNIT_MM }, */
2471 /* { 55 , 210 , 210 , 297 , GTK_UNIT_MM }, */
2472 { 60 , 210 , 210 , 330 , GTK_UNIT_MM },
2473 { 1 , 215 , 8.5 , 11 , GTK_UNIT_INCH },
2474 /* { 2 , 215 , 8.5 , 11 , GTK_UNIT_INCH }, */
2475 /* { 18 , 215 , 8.5 , 11 , GTK_UNIT_INCH }, */
2476 { 40 , 215 , 8.5 , 12 , GTK_UNIT_INCH },
2477 { 59 , 215 , 8.5 , 12.69 , GTK_UNIT_INCH },
2478 { 14 , 215 , 8.5 , 13 , GTK_UNIT_INCH },
2479 /* { 41 , 215 , 8.5 , 13 , GTK_UNIT_INCH }, */
2480 { 5 , 215 , 8.5 , 14 , GTK_UNIT_INCH },
2481 { 106 , 215 , 215 , 146 , GTK_UNIT_MM },
2482 { 15 , 215 , 215 , 275 , GTK_UNIT_MM },
2483 { 72 , 216 , 216 , 277 , GTK_UNIT_MM },
2484 { 113 , 220 , 220 , 110 , GTK_UNIT_MM },
2485 { 47 , 220 , 220 , 220 , GTK_UNIT_MM },
2486 { 57 , 227 , 227 , 356 , GTK_UNIT_MM },
2487 { 44 , 228 , 9 , 11 , GTK_UNIT_INCH },
2488 { 30 , 229 , 229 , 324 , GTK_UNIT_MM },
2489 /* { 104 , 229 , 229 , 324 , GTK_UNIT_MM }, */
2490 { 114 , 230 , 230 , 120 , GTK_UNIT_MM },
2491 { 115 , 230 , 230 , 160 , GTK_UNIT_MM },
2492 { 50 , 235 , 9.275 , 12 , GTK_UNIT_INCH },
2493 /* { 56 , 235 , 9.275 , 12 , GTK_UNIT_INCH }, */
2494 { 51 , 235 , 9.275 , 15 , GTK_UNIT_INCH },
2495 { 91 , 235 , 235 , 105 , GTK_UNIT_MM },
2496 { 86 , 235 , 235 , 120 , GTK_UNIT_MM },
2497 { 53 , 236 , 236 , 322 , GTK_UNIT_MM },
2498 { 71 , 240 , 240 , 332 , GTK_UNIT_MM },
2499 { 12 , 250 , 250 , 353 , GTK_UNIT_MM },
2500 /* { 33 , 250 , 250 , 353 , GTK_UNIT_MM }, */
2501 { 42 , 250 , 250 , 353 , GTK_UNIT_MM },
2502 { 45 , 254 , 10 , 11 , GTK_UNIT_INCH },
2503 { 16 , 254 , 10 , 14 , GTK_UNIT_INCH },
2504 { 80 , 257 , 257 , 182 , GTK_UNIT_MM },
2505 { 85 , 277 , 277 , 216 , GTK_UNIT_MM },
2506 { 75 , 279 , 11 , 8.5 , GTK_UNIT_INCH },
2507 { 3 , 279 , 11 , 17 , GTK_UNIT_INCH },
2508 /* { 17 , 279 , 11 , 17 , GTK_UNIT_INCH }, */
2509 { 52 , 296 , 11.69 , 18 , GTK_UNIT_INCH },
2510 { 77 , 297 , 297 , 210 , GTK_UNIT_MM },
2511 { 8 , 297 , 297 , 420 , GTK_UNIT_MM },
2512 /* { 67 , 297 , 297 , 420 , GTK_UNIT_MM }, */
2513 { 90 , 304 , 12 , 11 , GTK_UNIT_INCH },
2514 { 58 , 305 , 305 , 487 , GTK_UNIT_MM },
2515 { 116 , 309 , 309 , 120 , GTK_UNIT_MM },
2516 { 63 , 322 , 322 , 445 , GTK_UNIT_MM },
2517 /* { 68 , 322 , 322 , 445 , GTK_UNIT_MM }, */
2518 { 117 , 324 , 324 , 229 , GTK_UNIT_MM },
2519 { 29 , 324 , 324 , 458 , GTK_UNIT_MM },
2520 /* { 105 , 324 , 324 , 458 , GTK_UNIT_MM }, */
2521 { 84 , 332 , 332 , 240 , GTK_UNIT_MM },
2522 { 79 , 364 , 364 , 257 , GTK_UNIT_MM },
2523 { 39 , 377 , 14.875 , 11 , GTK_UNIT_INCH },
2524 { 46 , 381 , 15 , 11 , GTK_UNIT_INCH },
2525 { 76 , 420 , 420 , 297 , GTK_UNIT_MM },
2526 { 66 , 420 , 420 , 594 , GTK_UNIT_MM },
2527 { 4 , 431 , 17 , 11 , GTK_UNIT_INCH },
2528 { 24 , 431 , 17 , 22 , GTK_UNIT_INCH },
2529 { 118 , 458 , 458 , 324 , GTK_UNIT_MM },
2530 { 25 , 558 , 22 , 34 , GTK_UNIT_INCH },
2531 { 26 , 863 , 34 , 44 , GTK_UNIT_INCH },
2532 {0,0,0,0,0 }};
2533 gchar const *psize_name;
2534 int width_mm;
2536 psize_name = gtk_paper_size_get_name (psize);
2537 if (0 == strcmp (psize_name, GTK_PAPER_NAME_LETTER))
2538 return 1;
2539 if (0 == strcmp (psize_name, GTK_PAPER_NAME_A4))
2540 return 9;
2541 if (0 == strcmp (psize_name, GTK_PAPER_NAME_A3))
2542 return 8;
2543 if (0 == strcmp (psize_name, GTK_PAPER_NAME_A5))
2544 return 11;
2545 if (0 == strcmp (psize_name, GTK_PAPER_NAME_B5))
2546 return 13;
2547 if (0 == strcmp (psize_name, GTK_PAPER_NAME_EXECUTIVE))
2548 return 7;
2549 if (0 == strcmp (psize_name, GTK_PAPER_NAME_LEGAL))
2550 return 5;
2552 width_mm = (int) gtk_paper_size_get_width (psize, GTK_UNIT_MM);
2554 for (paper_defs = paper; paper_defs->code > 0; paper_defs++) {
2555 if (width_mm < paper_defs->width_mm)
2556 return 0;
2557 if (width_mm == paper_defs->width_mm) {
2558 gdouble width = gtk_paper_size_get_width (psize, paper_defs->unit);
2559 gdouble height = gtk_paper_size_get_height (psize, paper_defs->unit);
2561 if (width == paper_defs->width && height == paper_defs->height)
2562 return paper_defs->code;
2565 return 0;
2568 static void
2569 xlsx_write_print_info_hf (XLSXWriteState *state, GsfXMLOut *xml,
2570 const GnmPrintHF *hf, const char *hftext)
2572 char *s = xls_header_footer_export (hf);
2574 gsf_xml_out_start_element (xml, hftext);
2575 gsf_xml_out_add_cstr (xml, NULL, s);
2576 gsf_xml_out_end_element (xml); /* hftext */
2578 g_free (s);
2581 static void
2582 xlsx_write_print_info (XLSXWriteState *state, GsfXMLOut *xml)
2584 GnmPrintInformation *pi = state->sheet->print_info;
2585 double h_margin, f_margin;
2586 double left;
2587 double right;
2588 double t_margin, b_margin;
2590 g_return_if_fail (pi != NULL);
2592 gsf_xml_out_start_element (xml, "printOptions");
2593 gsf_xml_out_end_element (xml); /* </printOptions> */
2595 gsf_xml_out_start_element (xml, "pageMargins");
2596 print_info_get_margins (pi, &h_margin, &f_margin, &left, &right,
2597 &t_margin, &b_margin);
2598 go_xml_out_add_double (xml, "left", left / 72.);
2599 go_xml_out_add_double (xml, "right", right / 72.);
2600 go_xml_out_add_double (xml, "top", t_margin / 72.);
2601 go_xml_out_add_double (xml, "bottom", b_margin / 72.);
2602 go_xml_out_add_double (xml, "header", h_margin / 72.);
2603 go_xml_out_add_double (xml, "footer", f_margin / 72.);
2604 gsf_xml_out_end_element (xml); /* </pageMargins> */
2606 gsf_xml_out_start_element (xml, "pageSetup");
2608 xlsx_add_bool (xml, "blackAndWhite", pi->print_black_and_white);
2609 switch (pi->comment_placement) {
2610 case GNM_PRINT_COMMENTS_IN_PLACE:
2611 gsf_xml_out_add_cstr_unchecked (xml, "cellComments", "asDisplayed");
2612 break;
2613 case GNM_PRINT_COMMENTS_AT_END:
2614 gsf_xml_out_add_cstr_unchecked (xml, "cellComments", "atEnd");
2615 break;
2616 case GNM_PRINT_COMMENTS_NONE:
2617 default:
2618 gsf_xml_out_add_cstr_unchecked (xml, "cellComments", "none");
2619 break;
2621 if (pi->n_copies > 0)
2622 gsf_xml_out_add_int (xml, "copies", pi->n_copies);
2623 xlsx_add_bool (xml, "draft", pi->print_as_draft);
2624 switch (pi->error_display) {
2625 case GNM_PRINT_ERRORS_AS_BLANK:
2626 gsf_xml_out_add_cstr_unchecked (xml, "errors", "blank");
2627 break;
2628 case GNM_PRINT_ERRORS_AS_DASHES:
2629 gsf_xml_out_add_cstr_unchecked (xml, "errors", "dash");
2630 break;
2631 case GNM_PRINT_ERRORS_AS_NA:
2632 gsf_xml_out_add_cstr_unchecked (xml, "errors", "NA");
2633 break;
2634 case GNM_PRINT_ERRORS_AS_DISPLAYED:
2635 default:
2636 gsf_xml_out_add_cstr_unchecked (xml, "errors", "displayed");
2637 break;
2639 if (pi->start_page >= 0)
2640 gsf_xml_out_add_int (xml, "firstPageNumber", pi->start_page);
2641 if (pi->scaling.dim.rows != 1)
2642 gsf_xml_out_add_int (xml, "fitToHeight", pi->scaling.dim.rows);
2643 if (pi->scaling.dim.cols != 1)
2644 gsf_xml_out_add_int (xml, "fitToWidth", pi->scaling.dim.cols);
2645 /* horizontalDpi skipped */
2646 /* id skipped */
2648 if (pi->page_setup) {
2649 GtkPageOrientation orient;
2651 orient = gtk_page_setup_get_orientation (pi->page_setup);
2652 switch (orient) {
2653 case GTK_PAGE_ORIENTATION_PORTRAIT:
2654 case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT:
2655 gsf_xml_out_add_cstr_unchecked (xml, "orientation", "portrait");
2656 break;
2657 case GTK_PAGE_ORIENTATION_LANDSCAPE:
2658 case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE:
2659 gsf_xml_out_add_cstr_unchecked (xml, "orientation", "landscape");
2660 break;
2661 default:
2662 gsf_xml_out_add_cstr_unchecked (xml, "orientation", "default");
2663 break;
2665 } else
2666 gsf_xml_out_add_cstr_unchecked (xml, "orientation", "default");
2668 gsf_xml_out_add_cstr_unchecked
2669 (xml, "pageOrder",
2670 pi->print_across_then_down ? "overThenDown" : "downThenOver");
2672 if (pi->page_setup) {
2673 GtkPaperSize *psize;
2674 int paper_code;
2676 psize = gtk_page_setup_get_paper_size (pi->page_setup);
2677 paper_code = xlsx_find_paper_code (psize);
2679 if (paper_code > 0)
2680 gsf_xml_out_add_int (xml, "paperSize", paper_code);
2681 else {
2682 gdouble width = gtk_paper_size_get_width (psize, GTK_UNIT_POINTS);
2683 gdouble height = gtk_paper_size_get_height (psize, GTK_UNIT_POINTS);
2685 xlsx_add_pt (xml, "paperHeight", height);
2686 xlsx_add_pt (xml, "paperWidth", width);
2690 if (pi->scaling.percentage.x > 0)
2691 gsf_xml_out_add_int (xml, "scale",
2692 (int)CLAMP (pi->scaling.percentage.x, 10, 400));
2693 xlsx_add_bool (xml, "useFirstPageNumber", (pi->start_page >= 0));
2694 /* usePrinterDefaults skipped */
2695 /* verticalDpi skipped */
2697 gsf_xml_out_end_element (xml); /* </pageSetup> */
2699 gsf_xml_out_start_element (xml, "headerFooter");
2700 xlsx_write_print_info_hf (state, xml, pi->header, "oddHeader");
2701 xlsx_write_print_info_hf (state, xml, pi->footer, "oddFooter");
2702 gsf_xml_out_end_element (xml); /* </headerFooter> */
2704 if (NULL != pi->page_breaks.v)
2705 xlsx_write_breaks (state, xml, pi->page_breaks.v);
2706 if (NULL != pi->page_breaks.h)
2707 xlsx_write_breaks (state, xml, pi->page_breaks.h);
2710 /**********************************************************************/
2712 static int
2713 by_val_int (G_GNUC_UNUSED gpointer key_a, gpointer val_a,
2714 G_GNUC_UNUSED gpointer key_b, gpointer val_b,
2715 G_GNUC_UNUSED gpointer user)
2717 return GPOINTER_TO_INT (val_a) - GPOINTER_TO_INT (val_b);
2720 static void
2721 write_comment_author (gpointer key, G_GNUC_UNUSED gpointer value, GsfXMLOut *xml)
2723 gsf_xml_out_start_element (xml, "author");
2724 gsf_xml_out_add_cstr_unchecked (xml, NULL, (char const *) key);
2725 gsf_xml_out_end_element (xml);
2728 static void
2729 xlsx_write_comments (XLSXWriteState *state, GsfOutput *sheet_part, GSList *objects)
2731 GsfXMLOut *xml;
2732 GHashTable *authors;
2733 unsigned author = 0;
2734 char const *authorname;
2735 GSList *ptr;
2736 SheetObjectAnchor const *anchor;
2737 PangoAttrList *attrs;
2738 char *name = g_strdup_printf ("comments%u.xml", ++state->comment);
2739 GsfOutput *comments_part = gsf_outfile_new_child_full (state->xl_dir, name, FALSE,
2740 "content-type", "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
2741 NULL);
2742 g_free (name);
2743 gsf_outfile_open_pkg_relate (GSF_OUTFILE_OPEN_PKG (comments_part),
2744 GSF_OUTFILE_OPEN_PKG (sheet_part), ns_rel_com);
2745 xml = gsf_xml_out_new (comments_part);
2746 gsf_xml_out_start_element (xml, "comments");
2747 gsf_xml_out_add_cstr_unchecked (xml, "xmlns", ns_ss);
2748 /* search for comments authors */
2749 authors = g_hash_table_new (g_str_hash, g_str_equal);
2750 for (ptr = objects; ptr; ptr = ptr->next) {
2751 authorname = cell_comment_author_get (GNM_CELL_COMMENT (ptr->data));
2752 if (authorname != NULL && !g_hash_table_lookup_extended (authors, authorname, NULL, NULL))
2753 g_hash_table_insert (authors, (gpointer) authorname, GUINT_TO_POINTER (author++));
2755 /* save authors */
2756 gsf_xml_out_start_element (xml, "authors");
2757 gnm_hash_table_foreach_ordered (authors, (GHFunc) write_comment_author,
2758 by_val_int, xml);
2759 gsf_xml_out_end_element (xml); /* </authors> */
2760 /* save comments */
2761 gsf_xml_out_start_element (xml, "commentList");
2762 for (ptr = objects; ptr; ptr = ptr->next) {
2763 gsf_xml_out_start_element (xml, "comment");
2764 anchor = sheet_object_get_anchor (ptr->data);
2765 gsf_xml_out_add_cstr_unchecked (xml, "ref", range_as_string (&anchor->cell_bound));
2766 authorname = cell_comment_author_get (GNM_CELL_COMMENT (ptr->data));
2767 if (authorname != NULL)
2768 gsf_xml_out_add_uint (xml, "authorId",
2769 GPOINTER_TO_UINT (g_hash_table_lookup (authors, authorname)));
2770 gsf_xml_out_start_element (xml, "text");
2771 /* Save text as rich text */
2772 g_object_get (ptr->data, "text", &name, "markup", &attrs, NULL);
2773 if (name && *name)
2774 xlsx_write_rich_text (xml, name, attrs, TRUE);
2775 g_free (name);
2776 pango_attr_list_unref (attrs);
2777 gsf_xml_out_end_element (xml); /* </text> */
2778 gsf_xml_out_end_element (xml); /* </comment> */
2780 gsf_xml_out_end_element (xml); /* </commentList> */
2781 g_hash_table_destroy (authors);
2782 gsf_xml_out_end_element (xml); /* </comments> */
2783 g_object_unref (xml);
2784 gsf_output_close (comments_part);
2785 g_object_unref (comments_part);
2788 #include "xlsx-write-drawing.c"
2790 static char const *
2791 xlsx_write_sheet (XLSXWriteState *state, GsfOutfile *wb_part, Sheet *sheet)
2793 char *name = g_strdup_printf ("sheet%u.xml", ++state->sheet_dir.count);
2794 GsfOutput *sheet_part = gsf_outfile_new_child_full
2795 (xlsx_dir_get (&state->sheet_dir), name, FALSE,
2796 "content-type", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
2797 NULL);
2798 char const *rId = gsf_outfile_open_pkg_relate (GSF_OUTFILE_OPEN_PKG (sheet_part),
2799 GSF_OUTFILE_OPEN_PKG (wb_part),
2800 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet");
2801 GsfXMLOut *xml;
2802 GnmRange extent, cell_extent;
2803 GSList *drawing_objs, *legacy_drawing_objs, *comments, *others, *objects, *p;
2804 char const *chart_drawing_rel_id = NULL;
2805 char const *legacy_drawing_rel_id = NULL;
2806 GnmStyle **col_styles;
2807 GnmPrintInformation *pi = NULL;
2808 GHashTable *zorder;
2809 int z;
2810 gboolean ext_tab_textcolor = FALSE;
2812 state->sheet = sheet;
2813 col_styles = sheet_style_most_common (state->sheet, TRUE);
2814 excel_sheet_extent (state->sheet, &extent, col_styles,
2815 XLSX_MAX_COLS, XLSX_MAX_ROWS, state->io_context);
2816 cell_extent = sheet_get_cells_extent (state->sheet);
2817 extent = range_union (&extent, &cell_extent);
2819 objects = sheet_objects_get (state->sheet, NULL, G_TYPE_NONE);
2820 drawing_objs = legacy_drawing_objs = comments = others = NULL;
2821 zorder = g_hash_table_new (g_direct_hash, g_direct_equal);
2822 for (p = objects, z = 1; p; p = p->next, z++) {
2823 SheetObject *so = p->data;
2825 g_hash_table_insert (zorder, so, GINT_TO_POINTER (z));
2827 if (GNM_IS_CELL_COMMENT (so)) {
2828 comments = g_slist_prepend (comments, so);
2829 legacy_drawing_objs = g_slist_prepend (legacy_drawing_objs, so);
2830 } else if (GNM_IS_SO_GRAPH (so) ||
2831 GNM_IS_SO_LINE (so) ||
2832 GNM_IS_SO_FILLED (so) ||
2833 GNM_IS_SO_IMAGE (so))
2834 drawing_objs = g_slist_prepend (drawing_objs, so);
2835 else if (GNM_IS_SOW_SCROLLBAR (so) || GNM_IS_SOW_SLIDER (so) ||
2836 GNM_IS_SOW_SPINBUTTON (so) ||
2837 GNM_IS_SOW_BUTTON (so) ||
2838 GNM_IS_SOW_RADIO_BUTTON (so) ||
2839 GNM_IS_SOW_CHECKBOX (so) ||
2840 GNM_IS_SOW_COMBO (so) ||
2841 GNM_IS_SOW_LIST (so))
2842 legacy_drawing_objs = g_slist_prepend (legacy_drawing_objs, so);
2843 else if (GNM_IS_FILTER_COMBO (so))
2844 ; /* Nothing here */
2845 else
2846 others = g_slist_prepend (others, so);
2848 g_slist_free (objects);
2850 if (comments) {
2851 comments = g_slist_reverse (comments);
2852 xlsx_write_comments (state, sheet_part, comments);
2853 g_slist_free (comments);
2856 if (drawing_objs) {
2857 drawing_objs = g_slist_reverse (drawing_objs);
2858 chart_drawing_rel_id = xlsx_write_drawing_objects (state, sheet_part, drawing_objs, zorder);
2859 g_slist_free (drawing_objs);
2862 if (legacy_drawing_objs) {
2863 legacy_drawing_objs = g_slist_reverse (legacy_drawing_objs);
2864 legacy_drawing_rel_id = xlsx_write_legacy_drawing_objects (state, sheet_part, legacy_drawing_objs, zorder);
2865 g_slist_free (legacy_drawing_objs);
2868 for (p = others; p; p = p->next) {
2869 SheetObject *so = p->data;
2870 char *name;
2872 g_object_get (so, "name", &name, NULL);
2873 g_warning ("Not exporting object %s of type %s",
2874 (name ? name : "?"),
2875 g_type_name (G_OBJECT_TYPE (so)));
2876 g_free (name);
2878 g_slist_free (others);
2880 g_hash_table_destroy (zorder);
2882 xml = gsf_xml_out_new (sheet_part);
2883 /* CT_Worksheet = */
2884 gsf_xml_out_start_element (xml, "worksheet");
2885 gsf_xml_out_add_cstr_unchecked (xml, "xmlns", ns_ss);
2886 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:r", ns_rel);
2887 if (state->with_extension)
2888 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:gnmx", ns_gnm_ext);
2890 /* element sheetPr { CT_SheetPr }?, */
2891 gsf_xml_out_start_element (xml, "sheetPr");
2893 if (NULL != state->sheet->tab_color) {
2894 gsf_xml_out_start_element (xml, "tabColor");
2895 xlsx_add_rgb (xml, "rgb", state->sheet->tab_color->go_color);
2896 gsf_xml_out_end_element (xml); /* </tabColor> */
2898 if (NULL != state->sheet->tab_text_color)
2899 ext_tab_textcolor = TRUE;
2901 pi = state->sheet->print_info;
2902 if (pi != NULL) {
2903 gsf_xml_out_start_element (xml, "pageSetUpPr");
2904 xlsx_add_bool (xml, "fitToPage", pi->scaling.type == PRINT_SCALE_FIT_PAGES);
2905 gsf_xml_out_end_element (xml); /* </pageSetUpPr> */
2908 gsf_xml_out_end_element (xml); /* </sheetPr> */
2910 /* element dimension { CT_SheetDimension }?, */
2911 gsf_xml_out_start_element (xml, "dimension");
2912 xlsx_add_range (xml, "ref", &extent);
2913 gsf_xml_out_end_element (xml); /* </dimension> */
2914 /* element sheetViews { CT_SheetViews }?, */
2915 gsf_xml_out_start_element (xml, "sheetViews");
2916 SHEET_FOREACH_VIEW (state->sheet, sv, xlsx_write_sheet_view (xml, sv););
2917 gsf_xml_out_end_element (xml); /* </sheetViews> */
2918 /* element sheetFormatPr { CT_SheetFormatPr }?, */
2919 gsf_xml_out_start_element (xml, "sheetFormatPr");
2920 go_xml_out_add_double (xml, "defaultRowHeight",
2921 sheet_row_get_default_size_pts (state->sheet));
2922 if (state->sheet->rows.max_outline_level > 0)
2923 gsf_xml_out_add_int (xml, "outlineLevelRow",
2924 state->sheet->rows.max_outline_level);
2925 if (state->sheet->cols.max_outline_level > 0)
2926 gsf_xml_out_add_int (xml, "outlineLevelCol",
2927 state->sheet->cols.max_outline_level);
2928 gsf_xml_out_end_element (xml); /* </sheetFormatPr> */
2929 /* element cols { CT_Cols }*, */
2930 xlsx_write_cols (state, xml, col_styles);
2931 /* element sheetData { CT_SheetData }, */
2932 xlsx_write_cells (state, xml, &extent, col_styles);
2933 /* element sheetCalcPr { CT_SheetCalcPr }?, */
2934 /* element sheetProtection { CT_SheetProtection }?, */
2935 xlsx_write_protection (state, xml);
2936 /* element protectedRanges { CT_ProtectedRanges }?, */
2937 /* element scenarios { CT_Scenarios }?, */
2938 /* element autoFilter { CT_AutoFilter }?, */
2939 xlsx_write_autofilters (state, xml);
2940 /* element sortState { CT_SortState }?, */
2941 /* element dataConsolidate { CT_DataConsolidate }?, */
2942 /* element customSheetViews { CT_CustomSheetViews }?, */
2943 /* element mergeCells { CT_MergeCells }?, */
2944 xlsx_write_merges (state, xml);
2945 /* element phoneticPr { CT_PhoneticPr }?, */
2946 /* element conditionalFormatting { CT_ConditionalFormatting }*, */
2947 xlsx_write_conditional_formatting (state, xml);
2948 /* element dataValidations { CT_DataValidations }?, */
2949 xlsx_write_validations (state, xml, &extent);
2950 /* element hyperlinks { CT_Hyperlinks }?, */
2951 xlsx_write_hlinks (state, xml, &extent);
2952 /* element printOptions { CT_PrintOptions }?, included in xlsx_write_print_info */
2953 /* element pageMargins { CT_PageMargins }?, included in xlsx_write_print_info */
2954 /* element pageSetup { CT_PageSetup }?, included in xlsx_write_print_info */
2955 /* element headerFooter { CT_HeaderFooter }?, included in xlsx_write_print_info */
2956 xlsx_write_print_info (state, xml);
2957 /* element rowBreaks { CT_PageBreak }?, */
2958 /* element colBreaks { CT_PageBreak }?, */
2959 /* element customProperties { CT_CustomProperties }?, */
2960 /* element cellWatches { CT_CellWatches }?, */
2961 /* element ignoredErrors { CT_IgnoredErrors }?, */
2962 /* element smartTags { CT_SmartTags }?, */
2963 /* element drawing { CT_Drawing }?, */
2964 if (NULL != chart_drawing_rel_id) {
2965 gsf_xml_out_start_element (xml, "drawing");
2966 gsf_xml_out_add_cstr_unchecked (xml, "r:id", chart_drawing_rel_id);
2967 gsf_xml_out_end_element (xml); /* </drawing> */
2969 /* element legacyDrawing { CT_LegacyDrawing }?, Deleted in edition 2 */
2970 if (NULL != legacy_drawing_rel_id) {
2971 gsf_xml_out_start_element (xml, "legacyDrawing");
2972 gsf_xml_out_add_cstr_unchecked (xml, "r:id", legacy_drawing_rel_id);
2973 gsf_xml_out_end_element (xml); /* </legacyDrawing> */
2975 /* element legacyDrawingHF { CT_LegacyDrawing }?, Deleted in edition 2 */
2976 /* element picture { CT_SheetBackgroundPicture }?, */
2977 /* element oleObjects { CT_OleObjects }?, */
2978 /* element controls { CT_Controls }?, */
2979 /* element webPublishItems { CT_WebPublishItems }?, */
2980 /* element tableParts { CT_TableParts }?, */
2982 if (state->with_extension && ext_tab_textcolor) {
2983 gsf_xml_out_start_element (xml, "extLst");
2984 gsf_xml_out_start_element (xml, "ext");
2985 gsf_xml_out_add_cstr_unchecked (xml, "uri", ns_gnm_ext);
2987 gsf_xml_out_start_element (xml, "gnmx:tabTextColor");
2988 xlsx_add_rgb (xml, "rgb", state->sheet->tab_text_color->go_color);
2989 gsf_xml_out_end_element (xml); /* </gnmx:tabTextColor> */
2991 gsf_xml_out_end_element (xml); /* "ext" */
2992 gsf_xml_out_end_element (xml); /* "extLst" */
2995 /* element extLst { CT_ExtensionList }? */
2996 gsf_xml_out_end_element (xml); /* </worksheet> */
2998 g_object_unref (xml);
2999 gsf_output_close (sheet_part);
3000 g_object_unref (sheet_part);
3001 g_free (name);
3002 g_free (col_styles);
3004 state->sheet = NULL;
3006 return rId;
3009 static void
3010 xlsx_write_named_expression (G_GNUC_UNUSED gpointer key, GnmNamedExpr *nexpr, XLSXClosure *closure)
3012 char *formula;
3014 g_return_if_fail (nexpr != NULL);
3015 if (!expr_name_is_active (nexpr))
3016 return;
3018 gsf_xml_out_start_element (closure->xml, "definedName");
3020 if (nexpr->is_permanent) {
3021 char const *expr_name = expr_name_name (nexpr);
3022 if (0 == strcmp (expr_name, "Print_Area"))
3023 gsf_xml_out_add_cstr (closure->xml, "name", "_xlnm.Print_Area");
3024 else if (0 == strcmp (expr_name, "Sheet_Title"))
3025 gsf_xml_out_add_cstr (closure->xml, "name", "_xlnm.Sheet_Title");
3026 else
3027 gsf_xml_out_add_cstr (closure->xml, "name", expr_name);
3028 } else {
3029 gsf_xml_out_add_cstr (closure->xml, "name", expr_name_name (nexpr));
3031 if (nexpr->pos.sheet != NULL)
3032 gsf_xml_out_add_int (closure->xml, "localSheetId",
3033 nexpr->pos.sheet->index_in_wb);
3035 formula = expr_name_as_string (nexpr, NULL, closure->state->convs);
3036 gsf_xml_out_add_cstr (closure->xml, NULL, formula);
3037 g_free (formula);
3039 gsf_xml_out_end_element (closure->xml);
3042 static void
3043 xlsx_write_definedNames (XLSXWriteState *state, GsfXMLOut *xml)
3045 XLSXClosure closure = {state, xml};
3047 gsf_xml_out_start_element (xml, "definedNames");
3048 workbook_foreach_name
3049 (state->base.wb, FALSE,
3050 (GHFunc)&xlsx_write_named_expression, &closure);
3051 gsf_xml_out_end_element (xml);
3054 static void
3055 xlsx_write_calcPR (XLSXWriteState *state, GsfXMLOut *xml)
3057 Workbook const *wb = state->base.wb;
3059 #warning Filter by defaults
3060 gsf_xml_out_start_element (xml, "calcPr");
3062 gsf_xml_out_add_cstr_unchecked (xml, "calcMode",
3063 wb->recalc_auto ? "auto" : "manual");
3065 xlsx_add_bool (xml, "iterate", wb->iteration.enabled);
3066 gsf_xml_out_add_int (xml, "iterateCount",
3067 wb->iteration.max_number);
3068 go_xml_out_add_double (xml, "iterateDelta",
3069 wb->iteration.tolerance);
3071 gsf_xml_out_end_element (xml);
3074 #include "xlsx-write-pivot.c"
3076 #include "xlsx-write-docprops.c"
3078 static gboolean
3079 rich_value_equal (GnmValue const *a, GnmValue const *b)
3081 return value_equal (a, b) &&
3082 go_format_eq (VALUE_FMT (a), VALUE_FMT (b));
3085 static guint
3086 rich_value_hash (GnmValue const *v)
3088 return value_hash (v) ^ GPOINTER_TO_UINT (VALUE_FMT (v));
3091 static void
3092 xlsx_write_workbook (XLSXWriteState *state, GsfOutfile *root_part)
3094 int i;
3095 GsfXMLOut *xml;
3096 GSList *cacheRefs;
3097 GPtrArray *sheetIds = g_ptr_array_new ();
3098 GsfOutfile *xl_dir = (GsfOutfile *)gsf_outfile_new_child (root_part, "xl", TRUE);
3099 GsfOutfile *wb_part = (GsfOutfile *)gsf_outfile_open_pkg_add_rel (xl_dir, "workbook.xml",
3100 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
3101 root_part,
3102 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
3103 GnmStyle *style = gnm_style_new_default ();
3105 state->xl_dir = xl_dir;
3106 state->shared_string_hash = g_hash_table_new_full
3107 ((GHashFunc)rich_value_hash, (GEqualFunc)rich_value_equal,
3108 (GDestroyNotify)value_release, NULL);
3109 state->shared_string_array = g_ptr_array_new ();
3110 state->styles_hash = g_hash_table_new_full
3111 (gnm_style_hash, (GEqualFunc)gnm_style_equal,
3112 (GDestroyNotify)gnm_style_unref, NULL);
3113 state->styles_array = g_ptr_array_new ();
3114 state->dxfs_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
3115 state->dxfs_array = g_ptr_array_new ();
3116 state->axids = g_hash_table_new (NULL, NULL);
3118 xlsx_get_style_id (state, style);
3119 gnm_style_unref (style);
3121 state->convs = xlsx_conventions_new (TRUE);
3122 xlsx_dir_init (&state->sheet_dir, state->xl_dir, "worksheets");
3123 xlsx_dir_init (&state->chart_dir, state->xl_dir, "charts");
3124 xlsx_dir_init (&state->drawing_dir, state->xl_dir, "drawings");
3125 xlsx_dir_init (&state->legacy_drawing_dir, NULL, NULL);
3126 xlsx_dir_init (&state->media_dir, state->xl_dir, "media");
3127 xlsx_dir_init (&state->pivotCache_dir, state->xl_dir, "pivotCache");
3128 xlsx_dir_init (&state->pivotTable_dir, state->xl_dir, "pivotTable");
3130 g_ptr_array_set_size (sheetIds, workbook_sheet_count (state->base.wb));
3131 for (i = 0 ; i < workbook_sheet_count (state->base.wb); i++) {
3132 Sheet *sheet = workbook_sheet_by_index (state->base.wb, i);
3133 const char *rId = xlsx_write_sheet (state, wb_part, sheet);
3134 g_ptr_array_index (sheetIds, i) = (gpointer)rId;
3137 xlsx_write_shared_strings (state, wb_part);
3138 xlsx_write_styles (state, wb_part);
3139 xlsx_write_docprops (state, root_part);
3140 cacheRefs = xlsx_write_pivots (state, wb_part);
3142 xml = gsf_xml_out_new (GSF_OUTPUT (wb_part));
3143 gsf_xml_out_start_element (xml, "workbook");
3144 gsf_xml_out_add_cstr_unchecked (xml, "xmlns", ns_ss);
3145 gsf_xml_out_add_cstr_unchecked (xml, "xmlns:r", ns_rel);
3146 /* Note the schema does not allow the attribute xml:space */
3148 gsf_xml_out_start_element (xml, "fileVersion");
3149 gsf_xml_out_add_int (xml, "lastEdited", 4);
3150 gsf_xml_out_add_int (xml, "lowestEdited", 4);
3151 gsf_xml_out_add_int (xml, "rupBuild", 3820);
3152 gsf_xml_out_end_element (xml);
3154 gsf_xml_out_start_element (xml, "workbookPr");
3155 gsf_xml_out_add_int (xml, "date1904",
3156 workbook_date_conv (state->base.wb)->use_1904
3157 ? 1 : 0);
3158 gsf_xml_out_end_element (xml);
3160 gsf_xml_out_start_element (xml, "bookViews");
3161 WORKBOOK_FOREACH_VIEW (state->base.wb, view, {
3162 int scale = 10; /* Guess */
3163 gsf_xml_out_start_element (xml, "workbookView");
3164 gsf_xml_out_add_int (xml, "activeTab",
3165 view->current_sheet->index_in_wb);
3166 if (view->preferred_width > 0)
3167 gsf_xml_out_add_int (xml, "windowWidth", view->preferred_width * scale);
3168 if (view->preferred_height > 0)
3169 gsf_xml_out_add_int (xml, "windowHeight", view->preferred_height * scale);
3170 gsf_xml_out_end_element (xml);
3172 gsf_xml_out_end_element (xml);
3174 gsf_xml_out_start_element (xml, "sheets");
3175 for (i = 0 ; i < workbook_sheet_count (state->base.wb); i++) {
3176 Sheet const *sheet = workbook_sheet_by_index (state->base.wb, i);
3177 gsf_xml_out_start_element (xml, "sheet");
3178 gsf_xml_out_add_cstr (xml, "name", sheet->name_unquoted);
3179 gsf_xml_out_add_int (xml, "sheetId", i+1); /* FIXME What is this ?? */
3180 gsf_xml_out_add_cstr_unchecked (xml, "r:id",
3181 g_ptr_array_index (sheetIds, i));
3182 gsf_xml_out_end_element (xml); /* </sheet> */
3184 gsf_xml_out_end_element (xml); /* </sheets> */
3186 xlsx_write_definedNames (state, xml);
3188 xlsx_write_calcPR (state, xml);
3190 if (NULL != cacheRefs) {
3191 GSList *ptr;
3192 unsigned int i = 0;
3193 gsf_xml_out_start_element (xml, "pivotCaches");
3194 for (ptr = cacheRefs ; ptr != NULL ; ptr = ptr->next) {
3195 gsf_xml_out_start_element (xml, "pivotCache");
3196 gsf_xml_out_add_int (xml, "cacheId", i++);
3197 gsf_xml_out_add_cstr_unchecked (xml, "r:id", ptr->data);
3198 gsf_xml_out_end_element (xml); /* </pivotCache> */
3200 gsf_xml_out_end_element (xml); /* </pivotCaches> */
3201 g_slist_free (cacheRefs);
3203 gsf_xml_out_start_element (xml, "webPublishing");
3204 xlsx_add_bool (xml, "allowPng", TRUE);
3205 xlsx_add_bool (xml, "css", FALSE);
3206 if (state->version == ECMA_376_2006)
3207 gsf_xml_out_add_int (xml, "codePage", 1252); /* FIXME : Use utf-8 ? */
3208 else
3209 gsf_xml_out_add_cstr_unchecked (xml, "characterSet", "UTF-8");
3210 gsf_xml_out_end_element (xml);
3212 gsf_xml_out_end_element (xml); /* </workbook> */
3213 g_object_unref (xml);
3215 xlsx_conventions_free (state->convs);
3216 g_hash_table_destroy (state->shared_string_hash);
3217 g_ptr_array_free (state->shared_string_array, TRUE);
3218 g_hash_table_destroy (state->styles_hash);
3219 g_ptr_array_free (state->styles_array, TRUE);
3220 g_hash_table_destroy (state->dxfs_hash);
3221 g_ptr_array_free (state->dxfs_array, TRUE);
3222 g_hash_table_destroy (state->axids);
3224 xlsx_dir_close (&state->sheet_dir);
3225 xlsx_dir_close (&state->chart_dir);
3226 xlsx_dir_close (&state->drawing_dir);
3227 xlsx_dir_close (&state->legacy_drawing_dir);
3228 xlsx_dir_close (&state->media_dir);
3229 xlsx_dir_close (&state->pivotCache_dir);
3230 xlsx_dir_close (&state->pivotTable_dir);
3232 gsf_output_close (GSF_OUTPUT (wb_part));
3233 g_object_unref (wb_part);
3235 gsf_output_close (GSF_OUTPUT (xl_dir));
3236 g_object_unref (xl_dir);
3238 g_ptr_array_free (sheetIds, TRUE);
3241 G_MODULE_EXPORT void
3242 xlsx_file_save (GOFileSaver const *fs, GOIOContext *io_context,
3243 gconstpointer wb_view, GsfOutput *output);
3244 void
3245 xlsx_file_save (G_GNUC_UNUSED GOFileSaver const *fs, GOIOContext *io_context,
3246 gconstpointer wb_view, GsfOutput *output)
3248 XLSXWriteState state;
3249 GsfOutfile *root_part;
3250 GnmLocale *locale;
3251 GsfOutfile *zip;
3253 locale = gnm_push_C_locale ();
3255 state.version = ECMA_376_2006;
3256 state.with_extension = TRUE;
3257 state.io_context = io_context;
3258 state.base.wb = wb_view_get_workbook (wb_view);
3259 state.comment = 0;
3260 state.custom_prop_id = 29;
3261 state.drawing_elem_id = 1024;
3263 zip = gsf_outfile_zip_new (output, NULL);
3264 root_part = gsf_outfile_open_pkg_new (zip);
3265 g_object_unref (zip);
3267 xlsx_write_workbook (&state, root_part);
3268 gsf_output_close (GSF_OUTPUT (root_part));
3269 g_object_unref (root_part);
3271 gnm_pop_C_locale (locale);
3274 G_MODULE_EXPORT void
3275 xlsx2_file_save (GOFileSaver const *fs, GOIOContext *io_context,
3276 gconstpointer wb_view, GsfOutput *output);
3277 void
3278 xlsx2_file_save (G_GNUC_UNUSED GOFileSaver const *fs, GOIOContext *io_context,
3279 gconstpointer wb_view, GsfOutput *output)
3281 XLSXWriteState state;
3282 GsfOutfile *root_part;
3283 GnmLocale *locale;
3284 GsfOutfile *zip;
3286 locale = gnm_push_C_locale ();
3287 state.version = ECMA_376_2008;
3288 state.with_extension = TRUE;
3289 state.io_context = io_context;
3290 state.base.wb = wb_view_get_workbook (wb_view);
3291 state.comment = 0;
3292 state.custom_prop_id = 29;
3293 state.drawing_elem_id = 1024;
3295 zip = gsf_outfile_zip_new (output, NULL);
3296 root_part = gsf_outfile_open_pkg_new (zip);
3297 g_object_unref (zip);
3299 xlsx_write_workbook (&state, root_part);
3300 gsf_output_close (GSF_OUTPUT (root_part));
3301 g_object_unref (root_part);
3303 gnm_pop_C_locale (locale);