1 /*
2  * html.c
3  *
4  * Copyright (C) 1999, 2000 Rasca, Berlin
5  * EMail: thron@gmx.de
6  * Copyright (c) 2001-2013 Andreas J. Guelzow
7  * EMail: aguelzow@pyrshep.ca
8  * Copyright 2013 Morten Welinder <terra@gnone.org>
9  *
10  * Contributors :
11  *   Almer. S. Tigelaar <almer1@dds.nl>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, see <https://www.gnu.org/licenses/>.
25  */
26 
27 #include <gnumeric-config.h>
28 #include <gnumeric.h>
29 #include <goffice/goffice.h>
30 #include <workbook-view.h>
31 #include <workbook.h>
32 #include <sheet-style.h>
33 #include <style.h>
34 #include <style-color.h>
35 #include "html.h"
36 #include <cell.h>
37 #include <sheet.h>
38 #include <sheet-merge.h>
39 #include <value.h>
40 #include "font.h"
41 #include <cellspan.h>
42 #include <style-border.h>
43 #include <rendered-value.h>
44 #include <style.h>
45 #include <hlink.h>
46 #include <gutils.h>
47 
48 #include <gsf/gsf-output.h>
49 #include <string.h>
50 
51 /*
52  * html_version_t:
53  *
54  * version selector
55  *
56  */
57 typedef enum {
58 	HTML40    = 0,
59 	HTML32    = 1,
60 	HTML40F   = 2,
61 	XHTML     = 3
62 } html_version_t;
63 
64 /*
65  * html_print_encoded:
66  *
67  * @output: the stream
68  * @str: the string
69  *
70  * print the string to output encoded all special chars
71  *
72  */
73 static void
html_print_encoded(GsfOutput * output,char const * str)74 html_print_encoded (GsfOutput *output, char const *str)
75 {
76 	gunichar c;
77 
78 	if (str == NULL)
79 		return;
80 	for (; *str != '\0' ; str = g_utf8_next_char (str)) {
81 		switch (*str) {
82 			case '<':
83 				gsf_output_puts (output, "&lt;");
84 				break;
85 			case '>':
86 				gsf_output_puts (output, "&gt;");
87 				break;
88 			case '&':
89 				gsf_output_puts (output, "&amp;");
90 				break;
91 			case '\"':
92 				gsf_output_puts (output, "&quot;");
93 				break;
94 			case '\n':
95 				gsf_output_puts (output, "<br>\n");
96 				break;
97 			case '\r':
98 				gsf_output_puts (output, "<br>\r");
99 				if( *(str+1) == '\n' ) {
100 					gsf_output_puts (output, "\n");
101 					str++;
102 				}
103 				break;
104 			default:
105 				c = g_utf8_get_char (str);
106 				if (((c >= 0x20) && (c < 0x80)) ||
107 				    (c == '\n') || (c == '\r') || (c == '\t'))
108 					gsf_output_write (output, 1, str);
109 				else
110 					gsf_output_printf (output, "&#%u;", c);
111 				break;
112 		}
113 	}
114 }
115 
116 static void
html_get_text_color(GnmCell * cell,GnmStyle const * style,guint * r,guint * g,guint * b)117 html_get_text_color (GnmCell *cell, GnmStyle const *style, guint *r, guint *g, guint *b)
118 {
119 	GOColor fore = gnm_cell_get_render_color (cell);
120 
121 	if (fore == 0)
122 		*r = *g = *b = 0;
123 	else {
124 		*r = GO_COLOR_UINT_R (fore);
125 		*g = GO_COLOR_UINT_G (fore);
126 		*b = GO_COLOR_UINT_B (fore);
127 	}
128 }
129 static void
html_get_back_color(GnmStyle const * style,guint * r,guint * g,guint * b)130 html_get_back_color (GnmStyle const *style, guint *r, guint *g, guint *b)
131 {
132 	GnmColor const *color = gnm_style_get_back_color (style);
133 	*r = GO_COLOR_UINT_R (color->go_color);
134 	*g = GO_COLOR_UINT_G (color->go_color);
135 	*b = GO_COLOR_UINT_B (color->go_color);
136 }
137 
138 /*****************************************************************************/
139 
140 
141 static void
cb_html_add_chars(GsfOutput * output,char const * text,int len)142 cb_html_add_chars (GsfOutput *output, char const *text, int len)
143 {
144 	char * str;
145 
146 	g_return_if_fail (len > 0);
147 
148 	str = g_strndup (text, len);
149 	html_print_encoded (output, str);
150 	g_free (str);
151 }
152 
153 static char const *
cb_html_attrs_as_string(GsfOutput * output,PangoAttribute * a,html_version_t version)154 cb_html_attrs_as_string (GsfOutput *output, PangoAttribute *a, html_version_t version)
155 {
156 /* 	PangoColor const *c; */
157 	char const *closure = NULL;
158 
159 	switch (a->klass->type) {
160 	case PANGO_ATTR_FAMILY :
161 		break; /* ignored */
162 	case PANGO_ATTR_SIZE :
163 		break; /* ignored */
164 	case PANGO_ATTR_RISE:
165 		if (((PangoAttrInt *)a)->value > 5) {
166 			gsf_output_puts (output, "<sup>");
167 			closure = "</sup>";
168 		} else if (((PangoAttrInt *)a)->value < -5) {
169 			gsf_output_puts (output, "<sub>");
170 			closure = "</sub>";
171 		}
172 		break;
173 	case PANGO_ATTR_STYLE :
174 		if (((PangoAttrInt *)a)->value == PANGO_STYLE_ITALIC) {
175 			gsf_output_puts (output, "<i>");
176 			closure = "</i>";
177 		}
178 		break;
179 	case PANGO_ATTR_WEIGHT :
180 		if (((PangoAttrInt *)a)->value > 600){
181 			gsf_output_puts (output, "<b>");
182 			closure = "</b>";
183 		}
184 		break;
185 	case PANGO_ATTR_STRIKETHROUGH :
186 		if (((PangoAttrInt *)a)->value == 1) {
187 			if (version == HTML32) {
188 				gsf_output_puts (output, "<strike>");
189 				closure = "</strike>";
190 			} else {
191 				gsf_output_puts
192 					(output,
193 					 "<span style=\"text-decoration: "
194 					 "line-through;\">");
195 				closure = "</span>";
196 			}
197 		}
198 		break;
199 	case PANGO_ATTR_UNDERLINE :
200 		if ((version != HTML40) &&
201 		    (((PangoAttrInt *)a)->value != PANGO_UNDERLINE_NONE)) {
202 			gsf_output_puts (output, "<u>");
203 			closure = "</u>";
204 		}
205 		break;
206 	case PANGO_ATTR_FOREGROUND :
207 /* 		c = &((PangoAttrColor *)a)->color; */
208 /* 		g_string_append_printf (accum, "[color=%02xx%02xx%02x", */
209 /* 			((c->red & 0xff00) >> 8), */
210 /* 			((c->green & 0xff00) >> 8), */
211 /* 			((c->blue & 0xff00) >> 8)); */
212 		break;/* ignored */
213 	default :
214 		if (a->klass->type ==
215 		    go_pango_attr_subscript_get_attr_type ()) {
216 			if (((GOPangoAttrSubscript *)a)->val) {
217 				gsf_output_puts (output, "<sub>");
218 				closure = "</sub>";
219 			}
220 		} else if (a->klass->type ==
221 			   go_pango_attr_superscript_get_attr_type ()) {
222 			if (((GOPangoAttrSuperscript *)a)->val) {
223 				gsf_output_puts (output, "<sup>");
224 				closure = "</sup>";
225 			}
226 		}
227 		break; /* ignored */
228 	}
229 
230 	return closure;
231 }
232 
233 static void
html_new_markup(GsfOutput * output,const PangoAttrList * markup,char const * text,html_version_t version)234 html_new_markup (GsfOutput *output, const PangoAttrList *markup, char const *text,
235 		 html_version_t version)
236 {
237 	int handled = 0;
238 	PangoAttrIterator * iter;
239 	int from, to;
240 	int len = strlen (text);
241 	GString *closure = g_string_new ("");
242 
243 	iter = pango_attr_list_get_iterator ((PangoAttrList *) markup);
244 
245 	do {
246 		GSList *list, *l;
247 
248 		g_string_erase (closure, 0, -1);
249 		pango_attr_iterator_range (iter, &from, &to);
250 		from = (from > len) ? len : from; /* Since "from" can be really big! */
251 		to = (to > len) ? len : to;       /* Since "to" can be really big!   */
252 		if (from > handled)
253 			cb_html_add_chars (output, text + handled, from - handled);
254 		list = pango_attr_iterator_get_attrs (iter);
255 		for (l = list; l != NULL; l = l->next) {
256 			char const *result = cb_html_attrs_as_string (output, l->data, version);
257 			if (result != NULL)
258 				g_string_prepend (closure, result);
259 		}
260 		g_slist_free (list);
261 		if (to > from)
262 			cb_html_add_chars (output, text + from, to - from);
263 		gsf_output_puts (output, closure->str);
264 		handled = to;
265 	} while (pango_attr_iterator_next (iter));
266 
267 	g_string_free (closure, TRUE);
268 	pango_attr_iterator_destroy (iter);
269 
270 	return;
271 }
272 
273 
274 /*****************************************************************************/
275 
276 static void
html_write_cell_content(GsfOutput * output,GnmCell * cell,GnmStyle const * style,html_version_t version)277 html_write_cell_content (GsfOutput *output, GnmCell *cell, GnmStyle const *style, html_version_t version)
278 {
279 	guint r = 0;
280 	guint g = 0;
281 	guint b = 0;
282 	char *rendered_string;
283 	gboolean hidden = gnm_style_get_contents_hidden (style);
284 	GnmHLink* hlink = gnm_style_get_hlink (style);
285 	char* hlink_target = NULL;
286 
287 	if (hlink) {
288 		const char *target = gnm_hlink_get_target (hlink);
289 		if (GNM_IS_HLINK_URL (hlink)) {
290 			hlink_target = go_url_encode (target, 1);
291 		} else if (GNM_IS_HLINK_EXTERNAL (hlink)) {
292 			char *et = go_url_encode (target, 1);
293 			hlink_target = g_strconcat ("file://", et, NULL);
294 			g_free (et);
295 		}
296 	}
297 
298 	if (version == HTML32 && hidden)
299 		gsf_output_puts (output, "<!-- 'HIDDEN DATA' -->");
300 	else {
301 		if (style != NULL) {
302 			if (gnm_style_get_font_italic (style))
303 				gsf_output_puts (output, "<i>");
304 			if (gnm_style_get_font_bold (style))
305 				gsf_output_puts (output, "<b>");
306 			if (gnm_style_get_font_uline (style) != UNDERLINE_NONE)
307 				gsf_output_puts (output, "<u>");
308 			if (font_is_monospaced (style))
309 				gsf_output_puts (output, "<tt>");
310 			if (gnm_style_get_font_strike (style)) {
311 				if (version == HTML32)
312 					gsf_output_puts (output, "<strike>");
313 				else
314 					gsf_output_puts (output,
315 							 "<span style=\"text-decoration: line-through;\">");
316 			}
317 			switch (gnm_style_get_font_script (style)) {
318 			case GO_FONT_SCRIPT_SUB:
319 				gsf_output_puts (output, "<sub>");
320 				break;
321 			case GO_FONT_SCRIPT_SUPER:
322 				gsf_output_puts (output, "<sup>");
323 				break;
324 			default:
325 				break;
326 			}
327 		}
328 
329 		if (hlink_target) {
330 			gsf_output_printf (output, "<a href=\"%s\">", hlink_target);
331 			g_free (hlink_target);
332 		}
333 
334 		if (cell != NULL) {
335 			const PangoAttrList * markup = NULL;
336 
337 			if (style != NULL && version != HTML40) {
338 				html_get_text_color (cell, style, &r, &g, &b);
339 				if (r > 0 || g > 0 || b > 0)
340 					gsf_output_printf (output, "<font color=\"#%02X%02X%02X\">", r, g, b);
341 			}
342 
343 			if (VALUE_IS_STRING (cell->value)
344 			    && (VALUE_FMT (cell->value) != NULL)
345 			    && go_format_is_markup (VALUE_FMT (cell->value)))
346 				markup = go_format_get_markup (VALUE_FMT (cell->value));
347 
348 			if (markup != NULL) {
349 				GString *str = g_string_new ("");
350 				value_get_as_gstring (cell->value, str, NULL);
351 				html_new_markup (output, markup, str->str, version);
352 				g_string_free (str, TRUE);
353 			} else {
354 				rendered_string = gnm_cell_get_rendered_text (cell);
355 				html_print_encoded (output, rendered_string);
356 				g_free (rendered_string);
357 			}
358 		}
359 
360 		if (r > 0 || g > 0 || b > 0)
361 			gsf_output_puts (output, "</font>");
362 		if (hlink_target)
363 			gsf_output_puts (output, "</a>");
364 		if (style != NULL) {
365 			if (gnm_style_get_font_strike (style)) {
366 				if (version == HTML32)
367 					gsf_output_puts (output, "</strike>");
368 				else
369 					gsf_output_puts (output, "</span>");
370 			}
371 			switch (gnm_style_get_font_script (style)) {
372 			case GO_FONT_SCRIPT_SUB:
373 				gsf_output_puts (output, "</sub>");
374 				break;
375 			case GO_FONT_SCRIPT_SUPER:
376 				gsf_output_puts (output, "</sup>");
377 				break;
378 			default:
379 				break;
380 			}
381 			if (font_is_monospaced (style))
382 				gsf_output_puts (output, "</tt>");
383 			if (gnm_style_get_font_uline (style) != UNDERLINE_NONE)
384 				gsf_output_puts (output, "</u>");
385 			if (gnm_style_get_font_bold (style))
386 				gsf_output_puts (output, "</b>");
387 			if (gnm_style_get_font_italic (style))
388 				gsf_output_puts (output, "</i>");
389 		}
390 	}
391 }
392 
393 static char *
html_get_border_style(GnmBorder * border)394 html_get_border_style (GnmBorder *border)
395 {
396 	GString *text = g_string_new (NULL);
397 	char *result;
398 
399 	switch (border->line_type) {
400 	case GNM_STYLE_BORDER_THIN:
401 		g_string_append (text, "thin solid");
402 		break;
403 	case GNM_STYLE_BORDER_MEDIUM:
404 		g_string_append (text, "medium solid");
405 		break;
406 	case GNM_STYLE_BORDER_DASHED:
407 		g_string_append (text, "thin dashed");
408 		break;
409 	case GNM_STYLE_BORDER_DOTTED:
410 		g_string_append (text, "thin dotted");
411 		break;
412 	case GNM_STYLE_BORDER_THICK:
413 		g_string_append (text, "thick solid");
414 		break;
415 	case GNM_STYLE_BORDER_DOUBLE:
416 		g_string_append (text, "thick double");
417 		break;
418 	case GNM_STYLE_BORDER_HAIR:
419 		g_string_append (text, "0.5pt solid");
420 		break;
421 	case GNM_STYLE_BORDER_MEDIUM_DASH:
422 		g_string_append (text, "medium dashed");
423 		break;
424 	case GNM_STYLE_BORDER_DASH_DOT:
425 		g_string_append (text, "thin dashed");
426 		break;
427 	case GNM_STYLE_BORDER_MEDIUM_DASH_DOT:
428 		g_string_append (text, "medium dashed");
429 		break;
430 	case GNM_STYLE_BORDER_DASH_DOT_DOT:
431 		g_string_append (text, "thin dotted");
432 		break;
433 	case GNM_STYLE_BORDER_MEDIUM_DASH_DOT_DOT:
434 		g_string_append (text, "medium dotted");
435 		break;
436 	case GNM_STYLE_BORDER_SLANTED_DASH_DOT:
437 		g_string_append (text, "thin dashed");
438 		break;
439 	default:
440 		break;
441 	}
442 
443 	if (border->color) {
444 		guint r, g, b;
445 		r = GO_COLOR_UINT_R (border->color->go_color);
446 		g = GO_COLOR_UINT_G (border->color->go_color);
447 		b = GO_COLOR_UINT_B (border->color->go_color);
448 		g_string_append_printf (text, " #%02X%02X%02X", r, g, b);
449 	}
450 
451 	result = text->str;
452 	g_string_free (text, FALSE);
453 	return result;
454 }
455 
456 static void
html_write_one_border_style_40(GsfOutput * output,GnmBorder * border,char const * border_name)457 html_write_one_border_style_40 (GsfOutput *output, GnmBorder *border, char const *border_name)
458 {
459 	char *text;
460 	text = html_get_border_style (border);
461 	if (text == NULL || strlen (text) == 0)
462 		return;
463 	gsf_output_printf (output, " %s:%s;", border_name, text);
464 	g_free (text);
465 }
466 
467 static void
html_write_border_style_40(GsfOutput * output,GnmStyle const * style)468 html_write_border_style_40 (GsfOutput *output, GnmStyle const *style)
469 {
470 	GnmBorder *border;
471 
472 	border = gnm_style_get_border (style, MSTYLE_BORDER_TOP);
473 	if (!gnm_style_border_is_blank (border))
474 		html_write_one_border_style_40 (output, border, "border-top");
475 	border = gnm_style_get_border (style, MSTYLE_BORDER_BOTTOM);
476 	if (!gnm_style_border_is_blank (border))
477 		html_write_one_border_style_40 (output, border, "border-bottom");
478 	border = gnm_style_get_border (style, MSTYLE_BORDER_LEFT);
479 	if (!gnm_style_border_is_blank (border))
480 		html_write_one_border_style_40 (output, border, "border-left");
481 	border = gnm_style_get_border (style, MSTYLE_BORDER_RIGHT);
482 	if (!gnm_style_border_is_blank (border))
483 		html_write_one_border_style_40 (output, border, "border-right");
484 }
485 
486 static void
html_write_border_style_40_for_merged_cell(GsfOutput * output,GnmStyle const * style,Sheet * sheet,gint row,gint col)487 html_write_border_style_40_for_merged_cell (GsfOutput *output, GnmStyle const *style,
488 					    Sheet *sheet, gint row, gint col)
489 {
490 	GnmBorder *border;
491 	GnmRange const *merge_range;
492 	GnmCellPos pos;
493 	pos.col = col;
494 	pos.row = row;
495 
496 
497 	border = gnm_style_get_border (style, MSTYLE_BORDER_TOP);
498 	if (!gnm_style_border_is_blank (border))
499 		html_write_one_border_style_40 (output, border, "border-top");
500 	border = gnm_style_get_border (style, MSTYLE_BORDER_LEFT);
501 	if (!gnm_style_border_is_blank (border))
502 		html_write_one_border_style_40 (output, border, "border-left");
503 
504 	merge_range = gnm_sheet_merge_contains_pos (sheet, &pos);
505 	if (merge_range != NULL) {
506 		style = sheet_style_get (sheet, merge_range->end.col, merge_range->end.row);
507 		if (style == NULL)
508 			return;
509 	}
510 
511 	border = gnm_style_get_border (style, MSTYLE_BORDER_BOTTOM);
512 	if (!gnm_style_border_is_blank (border))
513 		html_write_one_border_style_40 (output, border, "border-bottom");
514 	border = gnm_style_get_border (style, MSTYLE_BORDER_RIGHT);
515 	if (!gnm_style_border_is_blank (border))
516 		html_write_one_border_style_40 (output, border, "border-right");
517 }
518 
519 static void
write_cell(GsfOutput * output,Sheet * sheet,gint row,gint col,html_version_t version,gboolean is_merge)520 write_cell (GsfOutput *output, Sheet *sheet, gint row, gint col, html_version_t version, gboolean is_merge)
521 {
522 	GnmCell *cell;
523 	GnmStyle const *style;
524 	guint r, g, b;
525 
526 	style = sheet_style_get (sheet, col, row);
527 	if (style != NULL && version != HTML32 && version != HTML40 &&
528 	    gnm_style_get_pattern (style) != 0 &&
529 	    gnm_style_is_element_set (style, MSTYLE_COLOR_BACK)) {
530 		html_get_back_color (style, &r, &g, &b);
531 		gsf_output_printf (output, " bgcolor=\"#%02X%02X%02X\"", r, g, b);
532 	}
533 
534 	cell = sheet_cell_get (sheet, col, row);
535 	if (cell != NULL) {
536 
537 		switch (gnm_style_get_align_v (style)) {
538 		case GNM_VALIGN_TOP:
539 			gsf_output_puts (output, " valign=\"top\" ");
540 			break;
541 		case GNM_VALIGN_BOTTOM:
542 			gsf_output_puts (output, " valign=\"bottom\" ");
543 			break;
544 		case GNM_VALIGN_DISTRIBUTED:
545 		case GNM_VALIGN_CENTER:
546 			gsf_output_puts (output, " valign=\"center\" ");
547 			break;
548 		case GNM_VALIGN_JUSTIFY:
549 			gsf_output_puts (output, " valign=\"baseline\" ");
550 			break;
551 		default:
552 			break;
553 		}
554 		switch (gnm_style_default_halign (style, cell)) {
555 		case GNM_HALIGN_RIGHT:
556 			gsf_output_puts (output, " align=\"right\" ");
557 			break;
558 		case GNM_HALIGN_DISTRIBUTED:
559 		case GNM_HALIGN_CENTER:
560 		case GNM_HALIGN_CENTER_ACROSS_SELECTION:
561 			gsf_output_puts (output, " align=\"center\" ");
562 			break;
563 		case GNM_HALIGN_LEFT:
564 			gsf_output_puts (output, " align=\"left\" ");
565 			break;
566 		case GNM_HALIGN_JUSTIFY:
567 			gsf_output_puts (output, " align=\"justify\" ");
568 			break;
569 		default:
570 			break;
571 		}
572 
573 	}
574 	if (version == HTML40 || version == HTML40F  || version ==XHTML) {
575 		if (style != NULL) {
576 			gsf_output_printf (output, " style=\"");
577 			if (gnm_style_get_pattern (style) != 0 &&
578 			    gnm_style_is_element_set (style, MSTYLE_COLOR_BACK)) {
579 				html_get_back_color (style, &r, &g, &b);
580 				gsf_output_printf (output, "background:#%02X%02X%02X;", r, g, b);
581 			}
582 			if (cell != NULL) {
583 				gint size = (int) (gnm_style_get_font_size (style) + 0.5);
584 				gsf_output_printf (output, " font-size:%ipt;", size);
585 				html_get_text_color (cell, style, &r, &g, &b);
586 				if (r > 0 || g > 0 || b > 0)
587 					gsf_output_printf (output, " color:#%02X%02X%02X;", r, g, b);
588 				if (gnm_style_get_contents_hidden (style))
589 					gsf_output_puts (output, " visibility:hidden;");
590 			}
591 			if (is_merge)
592 				html_write_border_style_40_for_merged_cell (output, style, sheet, row, col);
593 			else
594 				html_write_border_style_40 (output, style);
595 			gsf_output_printf (output, "\"");
596 		}
597 	}
598 	gsf_output_printf (output, ">");
599 	html_write_cell_content (output, cell, style, version);
600 	gsf_output_puts (output, "</td>\n");
601 }
602 
603 
604 /*
605  * write_row:
606  *
607  * @output: the stream
608  * @sheet: the gnumeric sheet
609  * @row: the row number
610  *
611  * Set up a TD node for each cell in the given row, witht eh  appropriate
612  * colspan and rowspan.
613  * Call write_cell for each cell.
614  */
615 static void
write_row(GsfOutput * output,Sheet * sheet,gint row,GnmRange * range,html_version_t version)616 write_row (GsfOutput *output, Sheet *sheet, gint row, GnmRange *range, html_version_t version)
617 {
618 	gint col;
619 	ColRowInfo const *ri = sheet_row_get_info (sheet, row);
620 	if (ri->needs_respan)
621 		row_calc_spans ((ColRowInfo *) ri, row, sheet);
622 
623 	for (col = range->start.col; col <= range->end.col; col++) {
624 		CellSpanInfo const *the_span;
625 		GnmRange const *merge_range;
626 		GnmCellPos pos;
627 		pos.col = col;
628 		pos.row = row;
629 
630 		/* Is this a span */
631 		the_span = row_span_get (ri, col);
632 		if (the_span != NULL) {
633 			gsf_output_printf (output, "<td colspan=\"%i\" ", the_span->right - col + 1);
634 			write_cell (output, sheet, row, the_span->cell->pos.col, version, FALSE);
635 			col = the_span->right;
636 			continue;
637 		}
638 
639                 /* is this covered by a merge */
640 		merge_range = gnm_sheet_merge_contains_pos	(sheet, &pos);
641 		if (merge_range != NULL) {
642 			if (merge_range->start.col != col ||
643 			    merge_range->start.row != row)
644 				continue;
645 			gsf_output_printf (output, "<td colspan=\"%i\" rowspan=\"%i\" ",
646 				 merge_range->end.col - merge_range->start.col + 1,
647 				 merge_range->end.row - merge_range->start.row + 1);
648 			write_cell (output, sheet, row, col, version, TRUE);
649 			col = merge_range->end.col;
650 			continue;
651 		}
652 		gsf_output_puts (output, "<td ");
653 		write_cell (output, sheet, row, col, version, FALSE);
654 	}
655 }
656 
657 /*
658  * write_sheet:
659  *
660  * @output: the stream
661  * @sheet: the gnumeric sheet
662  *
663  * set up a table and call write_row for each row
664  */
665 static void
write_sheet(GsfOutput * output,Sheet * sheet,html_version_t version,GOFileSaveScope save_scope)666 write_sheet (GsfOutput *output, Sheet *sheet,
667 	     html_version_t version, GOFileSaveScope save_scope)
668 {
669 	GnmRange total_range;
670 	gint row;
671 
672 	switch (version) {
673 	case HTML40:
674 	case HTML40F:
675 	case XHTML:
676 		gsf_output_puts (output, "<p></p><table cellspacing=\"0\" cellpadding=\"3\">\n");
677 		break;
678 	default:
679 		gsf_output_puts (output, "<p><table border=\"1\">\n");
680 		break;
681 	}
682 
683 	if (save_scope != GO_FILE_SAVE_RANGE) {
684 		gsf_output_puts (output, "<caption>");
685 		html_print_encoded (output, sheet->name_unquoted);
686 		gsf_output_puts (output, "</caption>\n");
687 	}
688 	total_range = sheet_get_extent (sheet, TRUE, TRUE);
689 	for (row = total_range.start.row; row <=  total_range.end.row; row++) {
690 		gsf_output_puts (output, "<tr>\n");
691 		write_row (output, sheet, row, &total_range, version);
692 		gsf_output_puts (output, "</tr>\n");
693 	}
694 	gsf_output_puts (output, "</table>\n");
695 }
696 
697 /*
698  * html_file_save:
699  *
700  * write the html file (version of html according to version argument)
701  */
702 static void
html_file_save(GOFileSaver const * fs,GOIOContext * io_context,WorkbookView const * wb_view,GsfOutput * output,html_version_t version)703 html_file_save (GOFileSaver const *fs, GOIOContext *io_context,
704 		WorkbookView const *wb_view, GsfOutput *output, html_version_t version)
705 {
706 	Workbook *wb = wb_view_get_workbook (wb_view);
707 	GOFileSaveScope save_scope;
708 	GPtrArray *sel;
709 	unsigned ui;
710 
711 	g_return_if_fail (fs != NULL);
712 	g_return_if_fail (wb != NULL);
713 	g_return_if_fail (output != NULL);
714 
715 	switch (version) {
716 	case HTML32:
717 	gsf_output_puts (output,
718 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
719 "<html>\n"
720 "<head>\n\t<title>Tables</title>\n"
721 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
722 "<meta name=\"generator\" content=\"Gnumeric " GNM_VERSION_FULL  " via " G_PLUGIN_FOR_HTML "\">\n"
723 "<style><!--\n"
724 "tt {\n"
725 "\tfont-family: courier;\n"
726 "}\n"
727 "td {\n"
728 "\tfont-family: helvetica, sans-serif;\n"
729 "}\n"
730 "caption {\n"
731 "\tfont-family: helvetica, sans-serif;\n"
732 "\tfont-size: 14pt;\n"
733 "\ttext-align: left;\n"
734 "}\n"
735 "--></style>\n"
736 "</head>\n<body>\n");
737 		break;
738 	case HTML40:
739 		gsf_output_puts (output,
740 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n"
741 "\t\t\"http://www.w3.org/TR/html4/strict.dtd\">\n"
742 "<html>\n"
743 "<head>\n\t<title>Tables</title>\n"
744 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
745 "<meta name=\"generator\" content=\"Gnumeric " GNM_VERSION_FULL  " via " G_PLUGIN_FOR_HTML "\">\n"
746 "<style type=\"text/css\">\n"
747 "tt {\n"
748 "\tfont-family: courier;\n"
749 "}\n"
750 "td {\n"
751 "\tfont-family: helvetica, sans-serif;\n"
752 "}\n"
753 "caption {\n"
754 "\tfont-family: helvetica, sans-serif;\n"
755 "\tfont-size: 14pt;\n"
756 "\ttext-align: left;\n"
757 "}\n"
758 "</style>\n"
759 "</head>\n<body>\n");
760 		break;
761 	case XHTML  :
762 		gsf_output_puts (output,
763 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
764 "\t\t\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
765 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
766 "<head>\n\t<title>Tables</title>\n"
767 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"
768 "<meta name=\"generator\" content=\"Gnumeric " GNM_VERSION_FULL  " via " G_PLUGIN_FOR_HTML "\" />\n"
769 "<style type=\"text/css\">\n"
770 "tt {\n"
771 "\tfont-family: courier;\n"
772 "}\n"
773 "td {\n"
774 "\tfont-family: helvetica, sans-serif;\n"
775 "}\n"
776 "caption {\n"
777 "\tfont-family: helvetica, sans-serif;\n"
778 "\tfont-size: 14pt;\n"
779 "\ttext-align: left;\n"
780 "}\n"
781 "</style>\n"
782 "</head>\n<body>\n");
783 		break;
784 	default:
785 		break;
786 	}
787 
788 	save_scope = go_file_saver_get_save_scope (fs);
789 
790 	sel = gnm_file_saver_get_sheets (fs, wb_view, TRUE);
791 	for (ui = 0; ui < sel->len; ui++) {
792 		Sheet *sheet = g_ptr_array_index (sel, ui);
793 		write_sheet (output, sheet, version, save_scope);
794 	}
795 	g_ptr_array_unref (sel);
796 
797 	if (version == HTML32 || version == HTML40 || version == XHTML)
798 		gsf_output_puts (output, "</body>\n</html>\n");
799 }
800 
801 void
html40_file_save(GOFileSaver const * fs,GOIOContext * io_context,WorkbookView const * wb_view,GsfOutput * output)802 html40_file_save (GOFileSaver const *fs, GOIOContext *io_context,
803                   WorkbookView const *wb_view, GsfOutput *output)
804 {
805 	html_file_save (fs, io_context, wb_view, output, HTML40);
806 }
807 
808 void
html32_file_save(GOFileSaver const * fs,GOIOContext * io_context,WorkbookView const * wb_view,GsfOutput * output)809 html32_file_save (GOFileSaver const *fs, GOIOContext *io_context,
810                   WorkbookView const *wb_view, GsfOutput *output)
811 {
812 	html_file_save (fs, io_context, wb_view, output, HTML32);
813 }
814 
815 void
html40frag_file_save(GOFileSaver const * fs,GOIOContext * io_context,WorkbookView const * wb_view,GsfOutput * output)816 html40frag_file_save (GOFileSaver const *fs, GOIOContext *io_context,
817 		      WorkbookView const *wb_view, GsfOutput *output)
818 {
819 	html_file_save (fs, io_context, wb_view, output, HTML40F);
820 }
821 
822 void
xhtml_file_save(GOFileSaver const * fs,GOIOContext * io_context,WorkbookView const * wb_view,GsfOutput * output)823 xhtml_file_save (GOFileSaver const *fs, GOIOContext *io_context,
824 		 WorkbookView const *wb_view, GsfOutput *output)
825 {
826 	html_file_save (fs, io_context, wb_view, output, XHTML);
827 }
828 
829 void
xhtml_range_file_save(GOFileSaver const * fs,GOIOContext * io_context,WorkbookView const * wb_view,GsfOutput * output)830 xhtml_range_file_save (GOFileSaver const *fs, GOIOContext *io_context,
831 		      WorkbookView const *wb_view, GsfOutput *output)
832 {
833 	/* Identical, but fs->save_scope is different */
834 	xhtml_file_save (fs, io_context, wb_view, output);
835 }
836