1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012, 2013, 2014, 2017,
3 2020 Free Software Foundation, Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
28 #include "data/file-name.h"
29 #include "data/file-handle-def.h"
30 #include "libpspp/assertion.h"
31 #include "libpspp/cast.h"
32 #include "libpspp/compiler.h"
33 #include "libpspp/i18n.h"
34 #include "libpspp/message.h"
35 #include "libpspp/version.h"
36 #include "output/cairo-chart.h"
37 #include "output/chart.h"
38 #include "output/driver-provider.h"
39 #include "output/options.h"
40 #include "output/output-item.h"
41 #include "output/pivot-output.h"
42 #include "output/pivot-table.h"
43 #include "output/table-provider.h"
45 #include "gl/minmax.h"
46 #include "gl/xalloc.h"
49 #define _(msgid) gettext (msgid)
51 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
57 struct output_driver driver
;
60 struct file_handle
*handle
;
61 char *chart_file_name
;
71 static const struct output_driver_class html_driver_class
;
73 static void html_output_table (struct html_driver
*,
74 const struct output_item
*);
75 static void escape_string (FILE *file
, const char *text
,
76 const char *space
, const char *newline
);
77 static void print_title_tag (FILE *file
, const char *name
,
80 static struct html_driver
*
81 html_driver_cast (struct output_driver
*driver
)
83 assert (driver
->class == &html_driver_class
);
84 return UP_CAST (driver
, struct html_driver
, driver
);
87 static struct driver_option
*
88 opt (struct output_driver
*d
, struct string_map
*options
, const char *key
,
89 const char *default_value
)
91 return driver_option_get (d
, options
, key
, default_value
);
95 put_header (struct html_driver
*html
)
97 fputs ("<!doctype html>\n", html
->file
);
98 fprintf (html
->file
, "<html");
99 char *ln
= get_language ();
101 fprintf (html
->file
, " lang=\"%s\"", ln
);
103 fprintf (html
->file
, ">\n");
104 fputs ("<head>\n", html
->file
);
105 print_title_tag (html
->file
, "title", _("PSPP Output"));
106 fprintf (html
->file
, "<meta name=\"generator\" content=\"%s\">\n", version
);
107 fputs ("<meta http-equiv=\"content-type\" "
108 "content=\"text/html; charset=utf-8\">\n", html
->file
);
115 " background: white;\n"
117 " padding: 0em 12em 0em 3em;\n"
121 " margin: 0pt 0pt 0pt 0em\n"
124 " text-indent: 1.5em;\n"
127 " font-size: 150%;\n"
128 " margin-left: -1.33em\n"
131 " font-size: 125%;\n"
132 " font-weight: bold;\n"
133 " margin-left: -.8em\n"
136 " font-size: 100%;\n"
137 " font-weight: bold;\n"
138 " margin-left: -.5em }\n"
140 " font-size: 100%;\n"
141 " margin-left: 0em\n"
143 "h1, h2, h3, h4, h5, h6 {\n"
144 " font-family: sans-serif;\n"
151 " font-family: sans-serif\n"
154 " border-collapse: collapse;\n"
155 " margin-bottom: 1em\n"
158 " text-align: left\n"
160 "th { font-weight: normal }\n"
174 fputs ("</head>\n", html
->file
);
175 fputs ("<body>\n", html
->file
);
178 static struct output_driver
*
179 html_create (struct file_handle
*fh
, enum settings_output_devices device_type
,
180 struct string_map
*o
)
182 struct output_driver
*d
;
183 struct html_driver
*html
= XZALLOC (struct html_driver
);
185 output_driver_init (&html
->driver
, &html_driver_class
, fh_get_file_name (fh
),
187 html
->bare
= parse_boolean (opt (d
, o
, "bare", "false"));
188 html
->css
= parse_boolean (opt (d
, o
, "css", "true"));
189 html
->borders
= parse_boolean (opt (d
, o
, "borders", "true"));
192 html
->chart_file_name
= parse_chart_file_name (opt (d
, o
, "charts",
193 fh_get_file_name (fh
)));
196 html
->bg
= parse_color (opt (d
, o
, "background-color", "#FFFFFFFFFFFF"));
197 html
->fg
= parse_color (opt (d
, o
, "foreground-color", "#000000000000"));
198 html
->file
= fn_open (html
->handle
, "w");
199 if (html
->file
== NULL
)
201 msg_error (errno
, _("error opening output file `%s'"), fh_get_file_name (html
->handle
));
211 output_driver_destroy (d
);
215 /* Emits <NAME>CONTENT</NAME> to the output, escaping CONTENT as
216 necessary for HTML. */
218 print_title_tag (FILE *file
, const char *name
, const char *content
)
222 fprintf (file
, "<%s>", name
);
223 escape_string (file
, content
, " ", " - ");
224 fprintf (file
, "</%s>\n", name
);
229 html_destroy (struct output_driver
*driver
)
231 struct html_driver
*html
= html_driver_cast (driver
);
233 if (html
->file
!= NULL
)
239 "<!-- end of file -->\n");
240 fn_close (html
->handle
, html
->file
);
242 free (html
->chart_file_name
);
243 fh_unref (html
->handle
);
248 html_submit__ (struct output_driver
*driver
, const struct output_item
*item
,
251 struct html_driver
*html
= html_driver_cast (driver
);
255 case OUTPUT_ITEM_CHART
:
256 if (html
->chart_file_name
)
258 char *file_name
= xr_draw_png_chart (item
->chart
,
259 html
->chart_file_name
,
261 &html
->fg
, &html
->bg
);
262 if (file_name
!= NULL
)
264 const char *title
= chart_get_title (item
->chart
);
265 fprintf (html
->file
, "<img src=\"%s\" alt=\"chart: %s\">",
266 file_name
, title
? title
: _("No description"));
272 case OUTPUT_ITEM_GROUP
:
273 for (size_t i
= 0; i
< item
->group
.n_children
; i
++)
274 html_submit__ (driver
, item
->group
.children
[i
], level
+ 1);
277 case OUTPUT_ITEM_IMAGE
:
278 if (html
->chart_file_name
)
280 char *file_name
= xr_write_png_image (
281 item
->image
, html
->chart_file_name
, ++html
->n_charts
);
282 if (file_name
!= NULL
)
284 fprintf (html
->file
, "<img src=\"%s\">", file_name
);
290 case OUTPUT_ITEM_MESSAGE
:
291 fprintf (html
->file
, "<p>");
293 char *s
= msg_to_string (item
->message
);
294 escape_string (html
->file
, s
, " ", "<br>");
297 fprintf (html
->file
, "</p>\n");
300 case OUTPUT_ITEM_PAGE_BREAK
:
303 case OUTPUT_ITEM_TABLE
:
304 html_output_table (html
, item
);
307 case OUTPUT_ITEM_TEXT
:
309 char *s
= text_item_get_plain_text (item
);
311 switch (item
->text
.subtype
)
313 case TEXT_ITEM_PAGE_TITLE
:
316 case TEXT_ITEM_TITLE
:
318 char tag
[3] = { 'H', MIN (5, level
) + '0', '\0' };
319 print_title_tag (html
->file
, tag
, s
);
323 case TEXT_ITEM_SYNTAX
:
324 fprintf (html
->file
, "<pre class=\"syntax\">");
325 escape_string (html
->file
, s
, " ", "<br>");
326 fprintf (html
->file
, "</pre>\n");
330 fprintf (html
->file
, "<p>");
331 escape_string (html
->file
, s
, " ", "<br>");
332 fprintf (html
->file
, "</p>\n");
343 html_submit (struct output_driver
*driver
, const struct output_item
*item
)
345 html_submit__ (driver
, item
, 1);
348 /* Write TEXT to file F, escaping characters as necessary for HTML. Spaces are
349 replaced by SPACE, which should be " " or " " New-lines are replaced by
350 NEWLINE, which might be "<BR>" or "\n" or something else appropriate. */
352 escape_string (FILE *file
, const char *text
,
353 const char *space
, const char *newline
)
363 fputs (newline
, file
);
366 fputs ("&", file
);
369 fputs ("<", file
);
372 fputs (">", file
);
378 fputs (""", file
);
388 border_to_css (int border
)
392 case TABLE_STROKE_NONE
:
395 case TABLE_STROKE_SOLID
:
398 case TABLE_STROKE_DASHED
:
401 case TABLE_STROKE_THICK
:
402 return "thick solid";
404 case TABLE_STROKE_THIN
:
407 case TABLE_STROKE_DOUBLE
:
423 style_start (struct css_style
*cs
, FILE *file
)
425 *cs
= (struct css_style
) {
432 style_end (struct css_style
*cs
)
434 if (cs
->n_styles
> 0)
435 fputs ("'", cs
->file
);
439 next_style (struct css_style
*st
)
441 bool first
= !st
->n_styles
++;
442 fputs (first
? " style='" : "; ", st
->file
);
446 put_style (struct css_style
*st
, const char *name
, const char *value
)
449 fprintf (st
->file
, "%s: %s", name
, value
);
453 format_color (const struct cell_color color
,
454 const struct cell_color default_color
,
455 char *buf
, size_t bufsize
)
457 bool retval
= !cell_color_equal (&color
, &default_color
);
460 if (color
.alpha
== 255)
461 snprintf (buf
, bufsize
, "#%02x%02x%02x", color
.r
, color
.g
, color
.b
);
463 snprintf (buf
, bufsize
, "rgba(%d, %d, %d, %.3f)",
464 color
.r
, color
.g
, color
.b
, color
.alpha
/ 255.0);
470 put_border (const struct table
*table
, const struct table_cell
*cell
,
471 struct css_style
*style
,
472 enum table_axis axis
, int h
, int v
,
473 const char *border_name
)
475 struct cell_color color
;
476 const char *css
= border_to_css (
477 table_get_rule (table
, axis
, cell
->d
[H
][h
], cell
->d
[V
][v
], &color
));
481 fprintf (style
->file
, "border-%s: %s", border_name
, css
);
484 if (format_color (color
, (struct cell_color
) CELL_COLOR_BLACK
,
486 fprintf (style
->file
, " %s", buf
);
491 html_put_table_cell (struct html_driver
*html
, const struct pivot_table
*pt
,
492 const struct table_cell
*cell
,
493 const char *tag
, const struct table
*t
)
495 fprintf (html
->file
, "<%s", tag
);
497 struct css_style style
;
498 style_start (&style
, html
->file
);
500 struct string body
= DS_EMPTY_INITIALIZER
;
501 bool numeric
= pivot_value_format_body (cell
->value
, pt
, &body
);
503 enum table_halign halign
= table_halign_interpret (cell
->cell_style
->halign
,
508 case TABLE_HALIGN_RIGHT
:
509 put_style (&style
, "text-align", "right");
511 case TABLE_HALIGN_CENTER
:
512 put_style (&style
, "text-align", "center");
519 if (cell
->options
& TABLE_CELL_ROTATE
)
520 put_style (&style
, "writing-mode", "sideways-lr");
522 if (cell
->cell_style
->valign
!= TABLE_VALIGN_TOP
)
524 put_style (&style
, "vertical-align",
525 (cell
->cell_style
->valign
== TABLE_VALIGN_BOTTOM
526 ? "bottom" : "middle"));
529 const struct font_style
*fs
= cell
->font_style
;
531 if (format_color (fs
->bg
[cell
->d
[V
][0] % 2],
532 (struct cell_color
) CELL_COLOR_WHITE
,
533 bgcolor
, sizeof bgcolor
))
534 put_style (&style
, "background", bgcolor
);
537 if (format_color (fs
->fg
[cell
->d
[V
][0] % 2],
538 (struct cell_color
) CELL_COLOR_BLACK
,
539 fgcolor
, sizeof fgcolor
))
540 put_style (&style
, "color", fgcolor
);
544 put_style (&style
, "font-family", "\"");
545 escape_string (html
->file
, fs
->typeface
, " ", "\n");
546 putc ('"', html
->file
);
549 put_style (&style
, "font-weight", "bold");
551 put_style (&style
, "font-style", "italic");
553 put_style (&style
, "text-decoration", "underline");
557 snprintf (buf
, sizeof buf
, "%dpt", fs
->size
);
558 put_style (&style
, "font-size", buf
);
561 if (t
&& html
->borders
)
563 put_border (t
, cell
, &style
, V
, 0, 0, "top");
564 put_border (t
, cell
, &style
, H
, 0, 0, "left");
566 if (cell
->d
[V
][1] == t
->n
[V
])
567 put_border (t
, cell
, &style
, V
, 0, 1, "bottom");
568 if (cell
->d
[H
][1] == t
->n
[H
])
569 put_border (t
, cell
, &style
, H
, 1, 0, "right");
573 int colspan
= table_cell_colspan (cell
);
575 fprintf (html
->file
, " colspan=\"%d\"", colspan
);
577 int rowspan
= table_cell_rowspan (cell
);
579 fprintf (html
->file
, " rowspan=\"%d\"", rowspan
);
581 putc ('>', html
->file
);
583 const char *s
= ds_cstr (&body
);
584 s
+= strspn (s
, CC_SPACES
);
585 escape_string (html
->file
, s
, " ", "<br>");
588 const struct pivot_value_ex
*ex
= pivot_value_ex (cell
->value
);
589 if (ex
->n_subscripts
)
591 fputs ("<sub>", html
->file
);
592 for (size_t i
= 0; i
< ex
->n_subscripts
; i
++)
595 putc (',', html
->file
);
596 escape_string (html
->file
, ex
->subscripts
[i
], " ", "<br>");
598 fputs ("</sub>", html
->file
);
600 if (ex
->n_footnotes
> 0)
602 fputs ("<sup>", html
->file
);
603 size_t n_footnotes
= 0;
604 for (size_t i
= 0; i
< ex
->n_footnotes
; i
++)
606 const struct pivot_footnote
*f
607 = pt
->footnotes
[ex
->footnote_indexes
[i
]];
610 if (n_footnotes
++ > 0)
611 putc (',', html
->file
);
613 char *marker
= pivot_footnote_marker_string (f
, pt
);
614 escape_string (html
->file
, marker
, " ", "<br>");
618 fputs ("</sup>", html
->file
);
621 /* output </th> or </td>. */
622 fprintf (html
->file
, "</%s>\n", tag
);
626 html_output_table_layer (struct html_driver
*html
, const struct pivot_table
*pt
,
627 const size_t *layer_indexes
)
629 struct table
*title
, *layers
, *body
, *caption
, *footnotes
;
630 pivot_output (pt
, layer_indexes
, true, &title
, &layers
, &body
,
631 &caption
, &footnotes
, NULL
, NULL
);
633 fputs ("<table", html
->file
);
636 fputs (" title=\"", html
->file
);
637 escape_string (html
->file
, pt
->notes
, " ", "\n");
638 putc ('"', html
->file
);
640 fputs (">\n", html
->file
);
644 struct table_cell cell
;
645 table_get_cell (title
, 0, 0, &cell
);
646 html_put_table_cell (html
, pt
, &cell
, "caption", NULL
);
651 fputs ("<thead>\n", html
->file
);
652 for (size_t y
= 0; y
< layers
->n
[V
]; y
++)
654 fputs ("<tr>\n", html
->file
);
656 struct table_cell cell
;
657 table_get_cell (layers
, 0, y
, &cell
);
658 cell
.d
[H
][1] = body
->n
[H
];
659 html_put_table_cell (html
, pt
, &cell
, "td", NULL
);
661 fputs ("</tr>\n", html
->file
);
663 fputs ("</thead>\n", html
->file
);
666 fputs ("<tbody>\n", html
->file
);
667 for (int y
= 0; y
< body
->n
[V
]; y
++)
669 fputs ("<tr>\n", html
->file
);
670 for (int x
= 0; x
< body
->n
[H
]; )
672 struct table_cell cell
;
673 table_get_cell (body
, x
, y
, &cell
);
674 if (x
== cell
.d
[TABLE_HORZ
][0] && y
== cell
.d
[TABLE_VERT
][0])
676 bool is_header
= (y
< body
->h
[V
][0]
677 || y
>= body
->n
[V
] - body
->h
[V
][1]
679 || x
>= body
->n
[H
] - body
->h
[H
][1]);
680 const char *tag
= is_header
? "th" : "td";
681 html_put_table_cell (html
, pt
, &cell
, tag
, body
);
684 x
= cell
.d
[TABLE_HORZ
][1];
686 fputs ("</tr>\n", html
->file
);
688 fputs ("</tbody>\n", html
->file
);
690 if (caption
|| footnotes
)
692 fprintf (html
->file
, "<tfoot>\n");
696 fputs ("<tr>\n", html
->file
);
698 struct table_cell cell
;
699 table_get_cell (caption
, 0, 0, &cell
);
700 cell
.d
[H
][1] = body
->n
[H
];
701 html_put_table_cell (html
, pt
, &cell
, "td", NULL
);
703 fputs ("</tr>\n", html
->file
);
707 for (size_t y
= 0; y
< footnotes
->n
[V
]; y
++)
709 fputs ("<tr>\n", html
->file
);
711 struct table_cell cell
;
712 table_get_cell (footnotes
, 0, y
, &cell
);
713 cell
.d
[H
][1] = body
->n
[H
];
714 html_put_table_cell (html
, pt
, &cell
, "td", NULL
);
716 fputs ("</tr>\n", html
->file
);
718 fputs ("</tfoot>\n", html
->file
);
721 fputs ("</table>\n\n", html
->file
);
724 table_unref (layers
);
726 table_unref (caption
);
727 table_unref (footnotes
);
731 html_output_table (struct html_driver
*html
, const struct output_item
*item
)
733 size_t *layer_indexes
;
734 PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes
, item
->table
, true)
735 html_output_table_layer (html
, item
->table
, layer_indexes
);
738 struct output_driver_factory html_driver_factory
=
739 { "html", "pspp.html", html_create
};
741 static const struct output_driver_class html_driver_class
=
744 .destroy
= html_destroy
,
745 .submit
= html_submit
,
746 .handles_groups
= true,