/* * HTML document generation * * Copyright (C) 2017 Patrick McDermott * * This file is part of Marquee. * * Marquee is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Marquee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Marquee. If not, see . */ #include "html.h" #include #include #include #include static const gchar *global_styles = /* * General styles */ "* {\n" "margin: 6px;\n" "padding: 0;\n" "background: none;\n" "border: 0 none;\n" "}\n" "html {\n" "margin: 0;\n" "}\n" "body {\n" "margin: 18px;\n" "background-color: #AFAFAF;\n" "font-family: sans-serif;\n" "}\n" "h1, h2, h3, h4, h5, h6, p {\n" "text-align: center;\n" "}\n" /* * Horizontal notebook */ "div.notebook-h {\n" "clear: left;\n" "position: relative;\n" "padding: 0;\n" "}\n" "div.notebook-h > input {\n" "display: none;\n" "}\n" "div.notebook-h > label {\n" "display: block;\n" "background-color: #9F9F9F;\n" "color: #5F5F5F;\n" "float: left;\n" "margin: 6px 3px 0 0;\n" "border-width: 1px 1px 0 1px;\n" "border-style: solid;\n" "border-color: #8F8F8F;\n" "border-radius: 3px 3px 0 0;\n" "padding: 6px;\n" "font-weight: bold;\n" "position: relative;\n" "top: 3px;\n" "}\n" "div.notebook-h > input:checked + label {\n" "background-color: #CFCFCF;\n" "color: #000000;\n" "padding: 6px 6px 7px 6px;\n" "border-color: #9F9F9F;\n" "position: relative;\n" "top: 0;\n" "z-index: 1;\n" "}\n" "div.notebook-h > div {\n" "display: none;\n" "background-color: #CFCFCF;\n" "clear: left;\n" "border: 1px solid #9F9F9F;\n" "border-radius: 0 0 3px 3px;\n" "position: relative;\n" "top: -1px;\n" "margin: 0;\n" "}\n" /* * Vertical notebook */ "div.notebook-v {\n" "position: relative;\n" /* Why are these weird margin and padding numbers necessary? */ "margin: 6px 5px 6px 0;\n" "padding: 6px 0 0 0;\n" "}\n" "div.notebook-v > input {\n" "display: none;\n" "}\n" "div.notebook-v > label {\n" "display: block;\n" "background-color: #9F9F9F;\n" "color: #5F5F5F;\n" "float: left;\n" "clear: left;\n" "margin: 0 0 3px 6px;\n" "border-width: 1px 0 1px 1px;\n" "border-style: solid;\n" "border-color: #8F8F8F;\n" "border-radius: 3px 0 0 3px;\n" "padding: 6px;\n" "font-weight: bold;\n" "position: relative;\n" "left: 3px;\n" "}\n" "div.notebook-v > input:checked + label {\n" "background-color: #CFCFCF;\n" "color: #000000;\n" "padding: 6px 7px 6px 6px;\n" "border-color: #9F9F9F;\n" "position: relative;\n" "left: 0;\n" "z-index: 1;\n" "}\n" "div.notebook-v > div {\n" "display: none;\n" "background-color: #CFCFCF;\n" "border: 1px solid #9F9F9F;\n" "border-radius: 0 3px 3px 0;\n" "position: relative;\n" "left: -1px;\n" "margin: 0;\n" "overflow: hidden;\n" "}\n" /* * Form elements */ "form {\n" "margin: 0;\n" "}\n" "form input, form select {\n" "border: 1px solid #9F9F9F;\n" "border-radius: 3px;\n" "background-color: #EFEFEF;\n" "padding: 6px;\n" "color: #000000;\n" "transition: all 250ms ease-in-out 0s;\n" "-moz-transition: all 250ms ease-in-out 0s;\n" "-wekbit-transition: all 250ms ease-in-out 0s;\n" "-o-transition: all 250ms ease-in-out 0s;\n" "}\n" "div.dialog-buttonbox {\n" "margin: 0;\n" "float: right;\n" "}\n" "form label {\n" "display: block;\n" "}\n" "form label span {\n" "display: inline-block;\n" "width: calc(50% - 6px);\n" "text-align: right;\n" "}\n" "form input[type=submit], form input[type=reset] {\n" "background-color: #DFDFDF;\n" "font-size: 16px;\n" "}\n" "form input:hover, form select:hover,\n" "form input:focus, form select:focus {\n" "border: 1px solid #4F8FCF;\n" "outline: 0;\n" "}\n" "form input[type=submit]:hover, form input[type=reset]:hover,\n" "form input[type=submit]:focus, form input[type=reset]:focus {\n" "background-color: #EFEFEF;\n" "}\n" "form input[type=submit]:disabled, form input[type=reset]:disabled {\n" "border: 1px solid #9F9F9F;\n" "background-color: #DFDFDF;\n" "color: #9F9F9F;\n" "}\n" "iframe {\n" "width: calc(100% - 12px);\n" "height: 400px;\n" "}\n" ; gchar * mq_html_document(const gchar *title, const gchar *styles, const gchar *head_tags, ...) { gsize len; va_list ap; gchar *child; gchar *document; gchar *ptr; /* Calculate length. */ len = strlen("\n\n\n\n"); len += strlen(title); len += strlen("\n\n"); if (head_tags) { len += strlen(head_tags); } len += strlen("\n\n"); va_start(ap, head_tags); while ((child = va_arg(ap, gchar *))) { len += strlen(child); } va_end(ap); len += strlen("\n\n"); ++len; /* NUL byte */ /* Build string. */ document = g_new(gchar, len); ptr = g_stpcpy(document, "\n\n\n\n"); ptr = g_stpcpy(ptr, title); ptr = g_stpcpy(ptr, "\n\n"); if (head_tags) { ptr = g_stpcpy(ptr, head_tags); } ptr = g_stpcpy(ptr, "\n\n"); va_start(ap, head_tags); while ((child = va_arg(ap, gchar *))) { ptr = g_stpcpy(ptr, child); g_free(child); } va_end(ap); ptr = g_stpcpy(ptr, "\n\n"); /* g_stpcpy() adds NUL. */ return document; } gchar * mq_html_document_v(const gchar *title, const gchar *styles, const gchar *head_tags, gchar **children) { gsize len; gsize i; gchar *document; gchar *ptr; /* Calculate length. */ len = strlen("\n\n\n\n"); len += strlen(title); len += strlen("\n\n"); if (head_tags) { len += strlen(head_tags); } len += strlen("\n\n"); for (i = 0; children && children[i]; ++i) { len += strlen(children[i]); } len += strlen("\n\n"); ++len; /* NUL byte */ /* Build string. */ document = g_new(gchar, len); ptr = g_stpcpy(document, "\n\n\n\n"); ptr = g_stpcpy(ptr, title); ptr = g_stpcpy(ptr, "\n\n"); if (head_tags) { ptr = g_stpcpy(ptr, head_tags); } ptr = g_stpcpy(ptr, "\n\n"); for (i = 0; children && children[i]; ++i) { ptr = g_stpcpy(ptr, children[i]); } ptr = g_stpcpy(ptr, "\n\n"); /* g_stpcpy() adds NUL. */ return document; } #define TEXT_ELEMENT(ELEM) \ gchar * \ mq_html_##ELEM(const gchar *text) \ { \ return g_strconcat("<" #ELEM ">", text, "\n", \ NULL); \ } \ gchar * \ mq_html_##ELEM##_free(gchar *text) \ { \ gchar *e; \ e = g_strconcat("<" #ELEM ">", text, "\n", NULL); \ g_free(text); \ return e; \ } TEXT_ELEMENT(h1) TEXT_ELEMENT(h2) TEXT_ELEMENT(h3) TEXT_ELEMENT(h4) TEXT_ELEMENT(h5) TEXT_ELEMENT(h6) TEXT_ELEMENT(p) #undef TEXT_ELEMENT gchar * mq_html_list(const gchar *type, GDestroyNotify destroy, ...) { gsize len; va_list ap; gchar *child; gchar *list; gchar *ptr; /* TODO: Doesn't support specification of
    list types. */ /* Calculate length. */ len = strlen("
      \n"); va_start(ap, destroy); while ((child = va_arg(ap, gchar *))) { len += strlen("
    • \n"); len += strlen(child); len += strlen("
    • \n"); } va_end(ap); len += strlen("
    \n"); ++len; /* NUL byte */ /* Build string. */ list = g_new(gchar, len); if (type && type[0]) { ptr = g_stpcpy(list, "
      \n"); } else { ptr = g_stpcpy(list, "
        \n"); } va_start(ap, destroy); while ((child = va_arg(ap, gchar *))) { ptr = g_stpcpy(ptr, "
      • \n"); ptr = g_stpcpy(ptr, child); if (destroy) { destroy(child); } ptr = g_stpcpy(ptr, "
      • \n"); } va_end(ap); ptr = g_stpcpy(ptr, "
      \n"); /* g_stpcpy() adds the NUL. */ return list; } gchar * mq_html_list_v(const gchar *type, gchar **children) { gsize len; gsize i; gchar *list; gchar *ptr; /* TODO: Doesn't support specification of
        list types. */ /* Calculate length. */ len = strlen("
          \n"); for (i = 0; children && children[i]; ++i) { len += strlen("
        • \n"); len += strlen(children[i]); len += strlen("
        • \n"); } len += strlen("
        \n"); ++len; /* NUL byte */ /* Build string. */ list = g_new(gchar, len); if (type && type[0]) { ptr = g_stpcpy(list, "
          \n"); } else { ptr = g_stpcpy(list, "
            \n"); } for (i = 0; children && children[i]; ++i) { ptr = g_stpcpy(ptr, "
          • \n"); ptr = g_stpcpy(ptr, children[i]); ptr = g_stpcpy(ptr, "
          • \n"); } ptr = g_stpcpy(ptr, "
          \n"); /* g_stpcpy() adds the NUL. */ return list; } gchar * mq_html_container(const gchar *element, const gchar *classes, ...) { gsize len; va_list ap; gchar *child; gchar *container; gchar *ptr; /* Calculate length. */ len = strlen("<"); len += strlen(element); if (classes && classes[0]) { len += strlen(" class=\""); len += strlen(classes); len += strlen("\""); } len += strlen(">\n"); va_start(ap, classes); while ((child = va_arg(ap, gchar *))) { len += strlen(child); } va_end(ap); len += strlen("\n"); ++len; /* NUL byte */ /* Build string. */ container = g_new(gchar, len); ptr = g_stpcpy(container, "<"); ptr = g_stpcpy(ptr, element); if (classes && classes[0]) { ptr = g_stpcpy(ptr, " class=\""); ptr = g_stpcpy(ptr, classes); ptr = g_stpcpy(ptr, "\""); } ptr = g_stpcpy(ptr, ">\n"); va_start(ap, classes); while ((child = va_arg(ap, gchar *))) { ptr = g_stpcpy(ptr, child); g_free(child); } va_end(ap); ptr = g_stpcpy(ptr, "\n"); /* g_stpcpy() adds the NUL. */ return container; } gchar * mq_html_container_v(const gchar *element, const gchar *classes, gchar **children) { gsize len; gsize i; gchar *container; gchar *ptr; /* Calculate length. */ len = strlen("<"); len += strlen(element); if (classes && classes[0]) { len += strlen(" class=\""); len += strlen(classes); len += strlen("\""); } len += strlen(">\n"); for (i = 0; children && children[i]; ++i) { len += strlen(children[i]); } len += strlen("\n"); ++len; /* NUL byte */ /* Build string. */ container = g_new(gchar, len); ptr = g_stpcpy(container, "<"); ptr = g_stpcpy(ptr, element); if (classes && classes[0]) { ptr = g_stpcpy(ptr, " class=\""); ptr = g_stpcpy(ptr, classes); ptr = g_stpcpy(ptr, "\""); } ptr = g_stpcpy(ptr, ">\n"); for (i = 0; children && children[i]; ++i) { ptr = g_stpcpy(ptr, children[i]); } ptr = g_stpcpy(ptr, "\n"); /* g_stpcpy() adds the NUL. */ return container; } gchar * mq_html_notebook(gboolean vertical, const gchar *name, guint current_page, ...) { gsize name_len; gsize len; va_list ap; gsize i; gchar *child; gchar *tab_label; gchar *i_str; gchar *notebook; gchar *ptr; /* Calculate length. */ name_len = strlen(name); len = strlen("
          \n"); va_start(ap, current_page); i = 0; while ((child = va_arg(ap, gchar *)) && (tab_label = va_arg(ap, gchar *))) { i_str = g_strdup_printf("%zu", i); len += strlen("\n"); len += strlen("\n"); ++i; } va_end(ap); va_start(ap, current_page); while ((child = va_arg(ap, gchar *)) && (tab_label = va_arg(ap, gchar *))) { i_str = g_strdup_printf("%zu", i); len += strlen("
          \n"); len += strlen(child); len += strlen("
          \n\n"); } va_end(ap); len += strlen("
          \n"); ++len; /* NUL byte */ /* Build string. */ notebook = g_new(gchar, len); ptr = g_stpcpy(notebook, "
          \n"); va_start(ap, current_page); i = 0; while ((child = va_arg(ap, gchar *)) && (tab_label = va_arg(ap, gchar *))) { i_str = g_strdup_printf("%zu", i); ptr = g_stpcpy(ptr, "\n"); ptr = g_stpcpy(ptr, "\n"); g_free(i_str); ++i; } va_end(ap); va_start(ap, current_page); i = 0; while ((child = va_arg(ap, gchar *)) && (tab_label = va_arg(ap, gchar *))) { i_str = g_strdup_printf("%zu", i); ptr = g_stpcpy(ptr, "
          \n"); ptr = g_stpcpy(ptr, child); g_free(child); ptr = g_stpcpy(ptr, "
          \n\n"); ++i; } va_end(ap); ptr = g_stpcpy(ptr, "
          \n"); /* g_stpcpy() adds the NUL. */ return notebook; } gchar * mq_html_form(const gchar *submit_name, const gchar *submit_label, const gchar *reset_name, const gchar *reset_label, ...) { gsize len; va_list ap; gchar *child; gchar *form; gchar *ptr; /* Calculate length. */ len = strlen("
          \n"); va_start(ap, reset_label); while ((child = va_arg(ap, gchar *))) { len += strlen(child); } va_end(ap); len += strlen("
          \n"); if (reset_label && reset_label[0]) { len += strlen("\n"); } if (submit_label && submit_label[0]) { len += strlen("\n"); } len += strlen("
          \n"); len += strlen("
          \n"); ++len; /* NUL byte */ /* Build string. */ form = g_new(gchar, len); ptr = g_stpcpy(form, "
          \n"); va_start(ap, reset_label); while ((child = va_arg(ap, gchar *))) { ptr = g_stpcpy(ptr, child); g_free(child); } va_end(ap); ptr = g_stpcpy(ptr, "
          \n"); if (reset_label && reset_label[0]) { ptr = g_stpcpy(ptr, "\n"); } if (submit_label && submit_label[0]) { ptr = g_stpcpy(ptr, "\n"); } ptr = g_stpcpy(ptr, "
          \n"); ptr = g_stpcpy(ptr, "
          \n"); /* g_stpcpy() adds the NUL. */ return form; } gchar * mq_html_form_v(const gchar *submit_name, const gchar *submit_label, const gchar *reset_name, const gchar *reset_label, gchar **children) { gsize len; gsize i; gchar *form; gchar *ptr; /* Calculate length. */ len = strlen("
          \n"); for (i = 0; children && children[i]; ++i) { len += strlen(children[i]); } len += strlen("
          \n"); if (reset_label && reset_label[0]) { len += strlen("\n"); } if (submit_label && submit_label[0]) { len += strlen("\n"); } len += strlen("
          \n"); len += strlen("
          \n"); ++len; /* NUL byte */ /* Build string. */ form = g_new(gchar, len); ptr = g_stpcpy(form, "
          \n"); for (i = 0; children && children[i]; ++i) { ptr = g_stpcpy(ptr, children[i]); } ptr = g_stpcpy(ptr, "
          \n"); if (reset_label && reset_label[0]) { ptr = g_stpcpy(ptr, "\n"); } if (submit_label && submit_label[0]) { ptr = g_stpcpy(ptr, "\n"); } ptr = g_stpcpy(ptr, "
          \n"); ptr = g_stpcpy(ptr, "
          \n"); /* g_stpcpy() adds the NUL. */ return form; } gchar * mq_html_input_text(const gchar *name, const gchar *label, const gchar *value, gboolean autofocus) { if (label && label[0]) { return g_strdup_printf( "\n", name, label, name, name, value ? value : "", autofocus ? " autofocus=\"autofocus\"" : ""); } else { return g_strdup_printf( "\n", name, name, value ? value : "", autofocus ? " autofocus=\"autofocus\"" : ""); } } gchar * mq_html_input_number_i(const gchar *name, const gchar *label, gint min, gint step, gint max, gint value) { if (label && label[0]) { return g_strdup_printf( "\n", name, label, name, name, min, step, max, value); } else { return g_strdup_printf( "\n", name, name, min, step, max, value); } } gchar * mq_html_input_number_d(const gchar *name, const gchar *label, gdouble min, gdouble step, gdouble max, gdouble value) { if (label && label[0]) { return g_strdup_printf( "\n", name, label, name, name, min, step, max, value); } else { return g_strdup_printf( "\n", name, name, min, step, max, value); } } gchar * mq_html_input_radio(const gchar *name, const gchar *id, const gchar *value, const gchar *label, gboolean checked) { if (label && label[0]) { return g_strdup_printf( "\n", id, label, name, id, value, checked ? " checked=\"checked\"" : ""); } else { return g_strdup_printf( "\n", name, id, value, checked ? " checked=\"checked\"" : ""); } } gchar * mq_html_input_checkbox(const gchar *name, const gchar *label, gboolean checked) { /* * Values of "checkbox"-type s that are not checked are not sent * in queries with form submissions. The "about:preferences" * query-handling code doesn't handle any preferences that aren't * submitted in the query, so values need to be submitted for unchecked * "checkbox"-type s in order for unchecking to have any effect. * * This function returns two s: one of type "hidden" with a value * of "off" and one of type "checkbox". If the "checkbox"-type * is checked, both values will be submitted in the query, with * the value of the "checkbox"-type last. Otherwise, only the * "off" value of the "hidden"-type will be submitted. * * The query-parsing code for "about"-scheme resources iterates through * each "key=value" pair in order, inserting each pair into a * GHashTable. When keys collide, g_hash_table_insert() replaces the * previous value with the new value. Thus, the value of the * "checkbox"-type will override that of the "hidden"-type * when the former is checked. */ if (label && label[0]) { return g_strdup_printf( "\n", name, label, name, name, name, checked ? " checked=\"checked\"" : ""); } else { return g_strdup_printf( "\n" "\n", name, name, name, checked ? " checked=\"checked\"" : ""); } } gchar * mq_html_input_select(const gchar *name, const gchar *label, const gchar *selected, GDestroyNotify destroy_value, GDestroyNotify destroy_label, ...) { gsize len; va_list ap; gchar *option_value; gchar *option_label; gchar *select; gchar *ptr; g_assert(selected && selected[0]); /* Would cause buffer overflow */ /* Calculate length. */ len = 0; if (label && label[0]) { len += strlen("\n"); } ++len; /* NUL byte */ /* Build string. */ ptr = select = g_new(gchar, len); if (label && label[0]) { ptr = g_stpcpy(ptr, "\n"); /* g_stpcpy() adds NUL */ } return select; } gchar * mq_html_input_hidden(const gchar *name, const gchar *value) { return g_strdup_printf( "\n", name, name, value ? value : ""); } gchar * mq_html_label(const gchar *for_id, const gchar *label, gboolean add_colon) { return g_strdup_printf( "\n", for_id, label, add_colon ? ":" : ""); } gchar * mq_html_submit(const gchar *name, const gchar *label, gboolean disabled) { return g_strdup_printf( "\n", name, label, disabled ? " disabled=\"disabled\"" : ""); } gchar * mq_html_buttonbox(const gchar *submit_name, const gchar *submit_label, const gchar *reset_name, const gchar *reset_label) { gsize len; gchar *buttonbox; gchar *ptr; /* Calculate length. */ len = strlen("
          \n"); if (reset_label && reset_label[0]) { len += strlen("\n"); } if (submit_label && submit_label[0]) { len += strlen("\n"); } len += strlen("
          \n"); ++len; /* NUL byte */ /* Build string. */ ptr = buttonbox = g_new(gchar, len); ptr = g_stpcpy(ptr, "
          \n"); if (reset_label && reset_label[0]) { ptr = g_stpcpy(ptr, "\n"); } if (submit_label && submit_label[0]) { ptr = g_stpcpy(ptr, "\n"); } ptr = g_stpcpy(ptr, "
          \n"); /* g_stpcpy() adds the NUL. */ return buttonbox; } gchar * mq_html_input_iframe(const gchar *src) { return g_strdup_printf("", src); }