aboutsummaryrefslogtreecommitdiff
path: root/src/html.cc
diff options
context:
space:
mode:
authorjcid <devnull@localhost>2007-10-07 00:36:34 +0200
committerjcid <devnull@localhost>2007-10-07 00:36:34 +0200
commit93715c46a99c96d6c866968312691ec9ab0f6a03 (patch)
tree573f19ec6aa740844f53a7c0eb7114f04096bf64 /src/html.cc
Initial revision
Diffstat (limited to 'src/html.cc')
-rw-r--r--src/html.cc5123
1 files changed, 5123 insertions, 0 deletions
diff --git a/src/html.cc b/src/html.cc
new file mode 100644
index 00000000..4661525a
--- /dev/null
+++ b/src/html.cc
@@ -0,0 +1,5123 @@
+/*
+ * File: html.cc
+ *
+ * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org>
+ *
+ * This program 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.
+ */
+
+/*
+ * Dillo HTML parsing routines
+ */
+
+/* Undefine if you want to unroll tables. For instance for PDAs */
+#define USE_TABLES
+
+/* Define to 1 to ignore white space immediately after an open tag,
+ * and immediately before a close tag. */
+#define SGML_SPCDEL 0
+
+
+#include <ctype.h> /* for isspace and tolower */
+#include <string.h> /* for memcpy and memmove */
+#include <stdlib.h>
+#include <stdio.h> /* for sprintf */
+#include <math.h> /* for rint */
+#include <errno.h>
+
+#include <fltk/utf.h> /* for utf8encode */
+
+#define DEBUG_LEVEL 10
+#include "debug.h"
+
+#include "msg.h"
+#include "binaryconst.h"
+#include "colors.h"
+
+#include "uicmd.hh"
+
+#define dillo_dbg_rendering 0
+
+#include "history.h"
+#include "nav.h"
+#include "menu.hh"
+#include "prefs.h"
+#include "misc.h"
+#include "capi.h"
+
+#include "html.hh"
+#include "dw/textblock.hh"
+#include "dw/bullet.hh"
+#include "dw/table.hh"
+#include "dw/tablecell.hh"
+#include "dw/listitem.hh"
+#include "dw/image.hh"
+#include "dw/ruler.hh"
+
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::ui;
+using namespace dw::core::style;
+
+typedef void (*TagOpenFunct) (DilloHtml *Html, char *Tag, int Tagsize);
+typedef void (*TagCloseFunct) (DilloHtml *Html, int TagIdx);
+
+#define TAB_SIZE 8
+
+// Dw to Textblock
+#define DW2TB(dw) ((Textblock*)dw)
+// "html struct" to "Layout"
+#define HT2LT(html) ((Layout*)html->bw->render_layout)
+// "Image" to "Dw Widget"
+#define IM2DW(Image) ((Widget*)Image->dw)
+// Top of the parsing stack
+#define S_TOP(html) (html->stack->getRef(html->stack->size()-1))
+
+/*
+ * Exported function with C linkage.
+ */
+extern "C" {
+void *a_Html_text(const char *type, void *P, CA_Callback_t *Call,void **Data);
+}
+
+/*
+ * Forward declarations
+ */
+static const char *Html_get_attr(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname);
+static const char *Html_get_attr2(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname,
+ int tag_parsing_flags);
+static char *Html_get_attr_wdef(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname,
+ const char *def);
+static void Html_add_widget(DilloHtml *html, Widget *widget,
+ char *width_str, char *height_str,
+ StyleAttrs *style_attrs);
+static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof);
+static void Html_write(DilloHtml *html, char *Buf, int BufSize, int Eof);
+static void Html_close(DilloHtml *html, int ClientKey);
+static void Html_callback(int Op, CacheClient_t *Client);
+static DilloHtml *Html_new(BrowserWindow *bw, const DilloUrl *url);
+static void Html_tag_open_input(DilloHtml *html, char *tag, int tagsize);
+static void Html_add_input(DilloHtmlForm *form,
+ DilloHtmlInputType type,
+ Widget *widget,
+ Embed *embed,
+ const char *name,
+ const char *init_str,
+ DilloHtmlSelect *select,
+ bool_t init_val);
+//static void Html_reset_form(GtkWidget *reset, DilloHtmlLB *html_lb);
+static int Html_tag_index(const char *tag);
+
+
+/*
+ * Local Data
+ */
+
+/* The following array of font sizes has to be _strictly_ crescent */
+static const int FontSizes[] = {8, 10, 12, 14, 18, 24};
+static const int FontSizesNum = 6;
+static const int FontSizesBase = 2;
+
+/* Parsing table structure */
+typedef struct {
+ char *name; /* element name */
+ unsigned char Flags; /* flags (explained near the table data) */
+ char EndTag; /* Is it Required, Optional or Forbidden */
+ uchar_t TagLevel; /* Used to heuristically parse bad HTML */
+ TagOpenFunct open; /* Open function */
+ TagCloseFunct close; /* Close function */
+} TagInfo;
+extern const TagInfo Tags[];
+
+/*
+ * Return the line number of the tag being processed by the parser.
+ */
+static int Html_get_line_number(DilloHtml *html)
+{
+ int i, ofs, line;
+ const char *p = html->Start_Buf;
+
+ dReturn_val_if_fail(p != NULL, -1);
+
+ ofs = html->CurrTagOfs;
+ line = html->OldTagLine;
+ for (i = html->OldTagOfs; i < ofs; ++i)
+ if (p[i] == '\n')
+ ++line;
+ html->OldTagOfs = html->CurrTagOfs;
+ html->OldTagLine = line;
+ return line;
+}
+
+/*
+ * Collect HTML error strings inside the linkblock.
+ */
+static void Html_msg(DilloHtml *html, const char *format, ... )
+{
+ va_list argp;
+
+ dStr_sprintfa(html->bw->page_bugs,
+ "HTML warning: line %d, ",
+ Html_get_line_number(html));
+ va_start(argp, format);
+ dStr_vsprintfa(html->bw->page_bugs, format, argp);
+ va_end(argp);
+ a_UIcmd_set_bug_prog(html->bw, ++html->bw->num_page_bugs);
+}
+
+/*
+ * Wrapper for a_Url_new that adds an error detection message.
+ * (if use_base_url is TRUE, html->linkblock->base_url is used)
+ */
+static DilloUrl *Html_url_new(DilloHtml *html,
+ const char *url_str, const char *base_url,
+ int flags, int32_t posx, int32_t posy,
+ int use_base_url)
+{
+ DilloUrl *url;
+ int n_ic, n_ic_spc;
+
+ url = a_Url_new(
+ url_str,
+ (use_base_url) ? base_url : URL_STR_(html->linkblock->base_url),
+ flags, posx, posy);
+ if ((n_ic = URL_ILLEGAL_CHARS(url)) != 0) {
+ const char *suffix = (n_ic) > 1 ? "s" : "";
+ n_ic_spc = URL_ILLEGAL_CHARS_SPC(url);
+ if (n_ic == n_ic_spc) {
+ MSG_HTML("URL has %d illegal character%s [%d space%s]\n",
+ n_ic, suffix, n_ic_spc, suffix);
+ } else if (n_ic_spc == 0) {
+ MSG_HTML("URL has %d illegal character%s [%d in (00-1F or 7F)]\n",
+ n_ic, suffix, n_ic);
+ } else {
+ MSG_HTML("URL has %d illegal character%s "
+ "[%d space%s and %d in (00-1F or 7F)]\n",
+ n_ic, suffix, n_ic_spc, n_ic_spc ? "s" : "", n_ic-n_ic_spc);
+ }
+ }
+ return url;
+}
+
+/*
+ * Set callback function and callback data for "html/text" MIME type.
+ */
+void *a_Html_text(const char *Type, void *P, CA_Callback_t *Call, void **Data)
+{
+ DilloWeb *web = (DilloWeb*)P;
+ DilloHtml *html = Html_new(web->bw, web->url);
+
+ *Data = (void *) html;
+ *Call = (CA_Callback_t) Html_callback;
+
+ return (void*) html->dw;
+}
+
+bool DilloHtmlLB::HtmlLinkReceiver::enter (Widget *widget, int link,
+ int x, int y)
+{
+ BrowserWindow *bw = this->lb->bw;
+
+ MSG(" ** ");
+ if (link == -1 && x == -1 && y == -1) {
+ _MSG(" Link LEAVE notify...\n");
+ a_UIcmd_set_msg(bw, "");
+ } else {
+ _MSG(" Link ENTER notify...\n");
+ a_UIcmd_set_msg(bw, "%s", URL_STR(this->lb->links->get(link)));
+ }
+ return true;
+}
+
+bool DilloHtmlLB::HtmlLinkReceiver::press (Widget *widget, int link,
+ int x, int y, EventButton *event)
+{
+ int ret = false;
+
+ _MSG("pressed button %d\n", event->button);
+ if (event->button == 3) {
+ a_UIcmd_page_popup(lb->bw, lb->links->get(link),
+ lb->bw->num_page_bugs ? lb->bw->page_bugs->str:NULL);
+ //a_UIcmd_link_popup(lb->bw, lb->links->get(link));
+ //a_UIcmd_bugmeter_popup(lb->bw);
+ ret = true;
+ }
+ return ret;
+}
+
+bool DilloHtmlLB::HtmlLinkReceiver::click (Widget *widget, int link,
+ int x, int y, EventButton *event)
+{
+ DilloUrl *url = lb->links->get(link);
+ _MSG("clicked on URL %d: %s\n", link, a_Url_str (url));
+
+
+ if (x != -1) {
+ char data[64];
+ snprintf(data, 64, "?%d,%d", x, y);
+ a_Url_set_ismap_coords(url, data);
+ }
+
+ if (event->button == 1) {
+ a_Nav_push(lb->bw, url);
+ } else if (event->button == 2) {
+ a_Nav_push_nw(lb->bw, url);
+ } else {
+ return false;
+ }
+
+ /* Change the link color to "visited" as visual feedback */
+ for (Widget *w = widget; w; w = w->getParent()) {
+ _MSG(" ->%s\n", w->getClassName());
+ if (w->instanceOf(dw::Textblock::CLASS_ID)) {
+ ((Textblock*)w)->changeLinkColor (link, lb->visited_color);
+ break;
+ }
+ }
+
+ return true;
+}
+
+
+/*
+ * We'll make the linkblock first to get it out of the way.
+ */
+static DilloHtmlLB *Html_lb_new(BrowserWindow *bw, const DilloUrl *url)
+{
+ DilloHtmlLB *html_lb = dNew(DilloHtmlLB, 1);
+
+ html_lb->bw = bw;
+ html_lb->base_url = a_Url_dup(url);
+ html_lb->linkReceiver = new DilloHtmlLB::HtmlLinkReceiver (html_lb);
+
+ html_lb->forms = new misc::SimpleVector <DilloHtmlForm> (1);
+
+ html_lb->links = new misc::SimpleVector <DilloUrl*> (64);
+
+ //a_Dw_image_map_list_init(&html_lb->maps);
+
+ html_lb->link_color = prefs.link_color;
+ html_lb->visited_color = prefs.visited_color;
+
+ return html_lb;
+}
+
+/*
+ * Free the memory used by the linkblock
+ */
+static void Html_lb_free(void *lb)
+{
+ int i, j, k;
+ DilloHtmlForm *form;
+ DilloHtmlInput *input_j;
+ DilloHtmlLB *html_lb = (DilloHtmlLB*)lb;
+
+ DEBUG_MSG(3, "Html_lb_free\n");
+
+ delete html_lb->linkReceiver;
+ a_Url_free(html_lb->base_url);
+
+ for (i = 0; i < html_lb->forms->size(); i++) {
+ form = html_lb->forms->getRef(i);
+ a_Url_free(form->action);
+ for (j = 0; j < form->inputs->size(); j++) {
+ input_j = form->inputs->getRef(j);
+ dFree(input_j->name);
+ dFree(input_j->init_str);
+
+ if (input_j->type == DILLO_HTML_INPUT_SELECT ||
+ input_j->type == DILLO_HTML_INPUT_SEL_LIST) {
+ for (k = 0; k < input_j->select->num_options; k++) {
+ dFree(input_j->select->options[k].value);
+ }
+ dFree(input_j->select->options);
+ dFree(input_j->select);
+ }
+ }
+ delete(form->inputs);
+ delete(form->form_receiver);
+ }
+ delete(html_lb->forms);
+
+ for (i = 0; i < html_lb->links->size(); i++)
+ if (html_lb->links->get(i))
+ a_Url_free(html_lb->links->get(i));
+ delete (html_lb->links);
+
+ //a_Dw_image_map_list_free(&html_lb->maps);
+
+ dFree(html_lb);
+}
+
+
+/*
+ * Set the URL data for image maps.
+ */
+//static void Html_set_link_coordinates(DilloHtmlLB *lb,
+// int link, int x, int y)
+//{
+// char data[64];
+//
+// if (x != -1) {
+// snprintf(data, 64, "?%d,%d", x, y);
+// a_Url_set_ismap_coords(lb->links->get(link), data);
+// }
+//}
+
+///*
+// * Handle the status function generated by the dw scroller,
+// * and show the url in the browser status-bar.
+// */
+//static void Html_handle_status(Widget *widget, int link, int x, int y,
+// DilloHtmlLB *lb)
+//{
+// DilloUrl *url;
+//
+// url = (link == -1) ? NULL : lb->links->get(link);
+// if (url) {
+// Html_set_link_coordinates(lb, link, x, y);
+// a_UIcmd_set_msg(lb->bw, "%s",
+// URL_ALT_(url) ? URL_ALT_(url) : URL_STR_(url));
+// lb->bw->status_is_link = 1;
+//
+// } else {
+// if (lb->bw->status_is_link)
+// a_UIcmd_set_msg(lb->bw, "");
+// }
+//}
+
+///*
+// * Activate a link ("link_clicked" callback of the page)
+// */
+//static bool_t Html_link_clicked(Widget *widget, int link, int x, int y,
+// EventButton *event, DilloHtmlLB *lb)
+//{
+// Html_set_link_coordinates(lb, link, x, y);
+// if (event->button == 1)
+// a_Nav_push(lb->bw, lb->links->get(link));
+// else if (event->button == 2) {
+// a_Nav_push_nw(lb->bw, lb->links->get(link));
+// } else {
+// return FALSE;
+// }
+//
+// if (widget->instanceOf (Textblock::CLASS_ID))
+// ((Textblock*)widget)->changeLinkColor (link, lb->visited_color);
+//
+// return TRUE;
+//}
+
+/*
+ * Popup the image menu ("button_press_event" callback of image)
+ */
+static bool_t Html_image_menu(Widget *widget,
+ EventButton *event,
+ BrowserWindow *bw)
+{
+// Image *image = (Image*)widget;
+//:AL
+// if (event->button == 3 && image->url) {
+// a_Menu_popup_set_url(bw, image->url);
+// a_Menu_popup_clear_url2(bw->menu_popup.over_image);
+//
+// gtk_menu_popup(GTK_MENU(bw->menu_popup.over_image), NULL, NULL,
+// NULL, NULL, event->button, ((DwMouseEvent*)event)->time);
+// return TRUE;
+// }
+
+ return FALSE;
+}
+
+/*
+ * Connect all signals of a textblock or an image.
+ */
+static void Html_connect_signals(DilloHtml *html, Widget *widget)
+{
+ widget->connectLink (html->linkblock->linkReceiver);
+}
+
+
+/*
+ * Create a new link in the linkblock, set it as the url's parent
+ * and return the index.
+ */
+static int Html_set_new_link(DilloHtml *html, DilloUrl **url)
+{
+ int nl = html->linkblock->links->size();
+ html->linkblock->links->increase();
+ html->linkblock->links->set(nl, (*url) ? *url : NULL);
+ return nl;
+}
+
+
+/*
+ * Allocate and insert form information into the Html linkblock
+ */
+static int Html_form_new(DilloHtmlLB *html_lb,
+ DilloHtmlMethod method,
+ const DilloUrl *action,
+ DilloHtmlEnc enc)
+{
+ DilloHtmlForm *form;
+
+ html_lb->forms->increase();
+ form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+ form->method = method;
+ form->action = a_Url_dup(action);
+ form->enc = enc;
+ form->inputs = new misc::SimpleVector <DilloHtmlInput> (4);
+ form->num_entry_fields = 0;
+ form->num_submit_buttons = 0;
+ form->form_receiver = new form::Form(html_lb);
+
+ _MSG("Html_form_new: action=%s nform=%d\n", action, nf);
+ return html_lb->forms->size();
+}
+
+
+/*
+ * Change one toplevel attribute. var should be an identifier. val is
+ * only evaluated once, so you can safely use a function call for it.
+ */
+#define HTML_SET_TOP_ATTR(html, var, val) \
+ do { \
+ StyleAttrs style_attrs; \
+ Style *old_style; \
+ \
+ old_style = S_TOP(html)->style; \
+ style_attrs = *old_style; \
+ style_attrs.var = (val); \
+ S_TOP(html)->style = \
+ Style::create (HT2LT(html), &style_attrs); \
+ old_style->unref (); \
+ } while (FALSE)
+
+
+
+/*
+ * Set the font at the top of the stack. BImask specifies which
+ * attributes in BI should be changed.
+ */
+static void Html_set_top_font(DilloHtml *html, char *name, int size,
+ int BI, int BImask)
+{
+ FontAttrs font_attrs;
+
+ font_attrs = *S_TOP(html)->style->font;
+ if (name)
+ font_attrs.name = name;
+ if (size)
+ font_attrs.size = size;
+ if (BImask & 1)
+ font_attrs.weight = (BI & 1) ? 700 : 400;
+ if (BImask & 2)
+ font_attrs.style = (BI & 2) ?
+ (prefs.use_oblique ?
+ FONT_STYLE_OBLIQUE : FONT_STYLE_ITALIC) :
+ FONT_STYLE_NORMAL;
+
+ HTML_SET_TOP_ATTR (html, font,
+ Font::create (HT2LT(html), &font_attrs));
+}
+
+/*
+ * Evaluates the ALIGN attribute (left|center|right|justify) and
+ * sets the style at the top of the stack.
+ */
+static void Html_tag_set_align_attr(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *align, *charattr;
+
+ if ((align = Html_get_attr(html, tag, tagsize, "align"))) {
+ if (dStrcasecmp (align, "left") == 0)
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_LEFT);
+ else if (dStrcasecmp (align, "right") == 0)
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_RIGHT);
+ else if (dStrcasecmp (align, "center") == 0)
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_CENTER);
+ else if (dStrcasecmp (align, "justify") == 0)
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_JUSTIFY);
+ else if (dStrcasecmp (align, "char") == 0) {
+ /* todo: Actually not supported for <p> etc. */
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_STRING);
+ if ((charattr = Html_get_attr(html, tag, tagsize, "char"))) {
+ if (charattr[0] == 0)
+ /* todo: ALIGN=" ", and even ALIGN="&32;" will reult in
+ * an empty string (don't know whether the latter is
+ * correct, has to be clarified with the specs), so
+ * that for empty strings, " " is assumed. */
+ HTML_SET_TOP_ATTR (html, textAlignChar, ' ');
+ else
+ HTML_SET_TOP_ATTR (html, textAlignChar, charattr[0]);
+ } else
+ /* todo: Examine LANG attr of <html>. */
+ HTML_SET_TOP_ATTR (html, textAlignChar, '.');
+ }
+ }
+}
+
+/*
+ * Evaluates the VALIGN attribute (top|bottom|middle|baseline) and
+ * sets the style in style_attrs. Returns TRUE when set.
+ */
+static bool_t Html_tag_set_valign_attr(DilloHtml *html, char *tag,
+ int tagsize, StyleAttrs *style_attrs)
+{
+ const char *attr;
+
+ if ((attr = Html_get_attr(html, tag, tagsize, "valign"))) {
+ if (dStrcasecmp (attr, "top") == 0)
+ style_attrs->valign = VALIGN_TOP;
+ else if (dStrcasecmp (attr, "bottom") == 0)
+ style_attrs->valign = VALIGN_BOTTOM;
+ else if (dStrcasecmp (attr, "baseline") == 0)
+ style_attrs->valign = VALIGN_BASELINE;
+ else
+ style_attrs->valign = VALIGN_MIDDLE;
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+
+/*
+ * Add a new DwPage into the current DwPage, for indentation.
+ * left and right are the horizontal indentation amounts, space is the
+ * vertical space around the block.
+ */
+static void Html_add_indented_widget(DilloHtml *html, Widget *textblock,
+ int left, int right, int space)
+{
+ StyleAttrs style_attrs;
+ Style *style;
+
+ style_attrs = *S_TOP(html)->style;
+
+ style_attrs.margin.setVal (0);
+ style_attrs.borderWidth.setVal (0);
+ style_attrs.padding.setVal(0);
+
+ /* Activate this for debugging */
+#if 0
+ style_attrs.borderWidth.setVal (1);
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html), style_attrs.color->getColor());
+ style_attrs.setBorderStyle (BORDER_DASHED);
+#endif
+
+ style_attrs.margin.left = left;
+ style_attrs.margin.right = right;
+ style = Style::create (HT2LT(html), &style_attrs);
+
+ DW2TB(html->dw)->addParbreak (space, style);
+ DW2TB(html->dw)->addWidget (textblock, style);
+ DW2TB(html->dw)->addParbreak (space, style);
+ S_TOP(html)->textblock = html->dw = textblock;
+ S_TOP(html)->hand_over_break = TRUE;
+ style->unref ();
+
+ /* Handle it when the user clicks on a link */
+ Html_connect_signals(html, textblock);
+}
+
+/*
+ * Create and add a new indented DwPage to the current DwPage
+ */
+static void Html_add_indented(DilloHtml *html, int left, int right, int space)
+{
+ Textblock *textblock = new Textblock (false);
+ Html_add_indented_widget (html, textblock, left, right, space);
+}
+
+/*
+ * Given a font_size, this will return the correct 'level'.
+ * (or the closest, if the exact level isn't found).
+ */
+static int Html_fontsize_to_level(int fontsize)
+{
+ int i, level;
+ double normalized_size = fontsize / prefs.font_factor,
+ approximation = FontSizes[FontSizesNum-1] + 1;
+
+ for (i = level = 0; i < FontSizesNum; i++)
+ if (approximation >= fabs(normalized_size - FontSizes[i])) {
+ approximation = fabs(normalized_size - FontSizes[i]);
+ level = i;
+ } else {
+ break;
+ }
+
+ return level;
+}
+
+/*
+ * Given a level of a font, this will return the correct 'size'.
+ */
+static int Html_level_to_fontsize(int level)
+{
+ level = MAX(0, level);
+ level = MIN(FontSizesNum - 1, level);
+
+ return (int)rint(FontSizes[level]*prefs.font_factor);
+}
+
+/*
+ * Miscelaneous initializations for a DwPage
+ */
+static void Html_set_dwpage(DilloHtml *html)
+{
+ Widget *widget;
+ Textblock *textblock;
+ StyleAttrs style_attrs;
+ FontAttrs font_attrs;
+
+ dReturn_if_fail (html->dw == NULL);
+
+ widget = textblock = new Textblock (false);
+ html->dw = html->stack->getRef(0)->textblock = widget;
+
+ /* Create a dummy font, attribute, and tag for the bottom of the stack. */
+ font_attrs.name = prefs.vw_fontname; /* Helvetica */
+ font_attrs.size = Html_level_to_fontsize(FontSizesBase);
+ font_attrs.weight = 400;
+ font_attrs.style = FONT_STYLE_NORMAL;
+
+ style_attrs.initValues ();
+ style_attrs.font = Font::create (HT2LT(html), &font_attrs);
+ style_attrs.color = Color::createSimple (HT2LT(html), prefs.text_color);
+ html->stack->getRef(0)->style = Style::create (HT2LT(html), &style_attrs);
+
+ html->stack->getRef(0)->table_cell_style = NULL;
+
+ /* Handle it when the user clicks on a link */
+ Html_connect_signals(html, widget);
+
+ html->bw->num_page_bugs = 0;
+ dStr_truncate(html->bw->page_bugs, 0);
+
+// gtk_signal_connect_while_alive (
+// GTK_OBJECT(GTK_BIN(html->bw->render_main_scroll)->child),
+// "button_press_event", GTK_SIGNAL_FUNC(Html_page_menu),
+// html->bw, GTK_OBJECT (page));
+//
+// /* Destroy the linkblock when the DwPage is destroyed */
+// gtk_signal_connect_object(GTK_OBJECT(page), "destroy",
+// GTK_SIGNAL_FUNC(Html_lb_free),
+// html->linkblock);
+}
+
+/*
+ * Create and initialize a new DilloHtml structure
+ */
+static DilloHtml *Html_new(BrowserWindow *bw, const DilloUrl *url)
+{
+ DilloHtml *html;
+
+ html = dNew(DilloHtml, 1);
+
+ html->Start_Buf = NULL;
+ html->Start_Ofs = 0;
+ html->CurrTagOfs = 0;
+ html->OldTagOfs = 0;
+ html->OldTagLine = 1;
+
+ html->DocType = DT_NONE; /* assume Tag Soup 0.0! :-) */
+ html->DocTypeVersion = 0.0f;
+
+ html->dw = NULL;
+ html->bw = bw;
+ html->linkblock = Html_lb_new(bw, url);
+
+ html->stack = new misc::SimpleVector <DilloHtmlState> (16);
+ html->stack->increase();
+ html->stack->getRef(0)->tag_name = dStrdup("none");
+ html->stack->getRef(0)->style = NULL;
+ html->stack->getRef(0)->table_cell_style = NULL;
+ html->stack->getRef(0)->parse_mode = DILLO_HTML_PARSE_MODE_INIT;
+ html->stack->getRef(0)->table_mode = DILLO_HTML_TABLE_MODE_NONE;
+ html->stack->getRef(0)->cell_text_align_set = FALSE;
+ html->stack->getRef(0)->list_type = HTML_LIST_NONE;
+ html->stack->getRef(0)->list_number = 0;
+ html->stack->getRef(0)->tag_idx = -1; /* MUST not be used */
+ html->stack->getRef(0)->textblock = NULL;
+ html->stack->getRef(0)->table = NULL;
+ html->stack->getRef(0)->ref_list_item = NULL;
+ html->stack->getRef(0)->current_bg_color = prefs.bg_color;
+ html->stack->getRef(0)->hand_over_break = FALSE;
+
+ html->Stash = dStr_new("");
+ html->StashSpace = FALSE;
+
+ html->SPCBuf = NULL;
+
+ html->pre_column = 0;
+ html->PreFirstChar = FALSE;
+ html->PrevWasCR = FALSE;
+ html->PrevWasOpenTag = FALSE;
+ html->SPCPending = FALSE;
+ html->InVisitedLink = FALSE;
+ html->ReqTagClose = FALSE;
+ html->CloseOneTag = FALSE;
+ html->TagSoup = TRUE;
+ html->NameVal = NULL;
+
+ html->Num_HTML = html->Num_HEAD = html->Num_BODY = html->Num_TITLE = 0;
+
+ html->InFlags = IN_NONE;
+
+ html->attr_data = dStr_sized_new(1024);
+
+ Html_set_dwpage(html);
+
+ return html;
+}
+
+/*
+ * Initialize the stash buffer
+ */
+static void Html_stash_init(DilloHtml *html)
+{
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_STASH;
+ html->StashSpace = FALSE;
+ dStr_truncate(html->Stash, 0);
+}
+
+/* Entities list from the HTML 4.01 DTD */
+typedef struct {
+ char *entity;
+ int isocode;
+} Ent_t;
+
+#define NumEnt 252
+static const Ent_t Entities[NumEnt] = {
+ {"AElig",0306}, {"Aacute",0301}, {"Acirc",0302}, {"Agrave",0300},
+ {"Alpha",01621},{"Aring",0305}, {"Atilde",0303}, {"Auml",0304},
+ {"Beta",01622}, {"Ccedil",0307}, {"Chi",01647}, {"Dagger",020041},
+ {"Delta",01624},{"ETH",0320}, {"Eacute",0311}, {"Ecirc",0312},
+ {"Egrave",0310},{"Epsilon",01625},{"Eta",01627}, {"Euml",0313},
+ {"Gamma",01623},{"Iacute",0315}, {"Icirc",0316}, {"Igrave",0314},
+ {"Iota",01631}, {"Iuml",0317}, {"Kappa",01632}, {"Lambda",01633},
+ {"Mu",01634}, {"Ntilde",0321}, {"Nu",01635}, {"OElig",0522},
+ {"Oacute",0323},{"Ocirc",0324}, {"Ograve",0322}, {"Omega",01651},
+ {"Omicron",01637},{"Oslash",0330},{"Otilde",0325},{"Ouml",0326},
+ {"Phi",01646}, {"Pi",01640}, {"Prime",020063},{"Psi",01650},
+ {"Rho",01641}, {"Scaron",0540}, {"Sigma",01643}, {"THORN",0336},
+ {"Tau",01644}, {"Theta",01630}, {"Uacute",0332}, {"Ucirc",0333},
+ {"Ugrave",0331},{"Upsilon",01645},{"Uuml",0334}, {"Xi",01636},
+ {"Yacute",0335},{"Yuml",0570}, {"Zeta",01626}, {"aacute",0341},
+ {"acirc",0342}, {"acute",0264}, {"aelig",0346}, {"agrave",0340},
+ {"alefsym",020465},{"alpha",01661},{"amp",38}, {"and",021047},
+ {"ang",021040}, {"aring",0345}, {"asymp",021110},{"atilde",0343},
+ {"auml",0344}, {"bdquo",020036},{"beta",01662}, {"brvbar",0246},
+ {"bull",020042},{"cap",021051}, {"ccedil",0347}, {"cedil",0270},
+ {"cent",0242}, {"chi",01707}, {"circ",01306}, {"clubs",023143},
+ {"cong",021105},{"copy",0251}, {"crarr",020665},{"cup",021052},
+ {"curren",0244},{"dArr",020723}, {"dagger",020040},{"darr",020623},
+ {"deg",0260}, {"delta",01664}, {"diams",023146},{"divide",0367},
+ {"eacute",0351},{"ecirc",0352}, {"egrave",0350}, {"empty",021005},
+ {"emsp",020003},{"ensp",020002}, {"epsilon",01665},{"equiv",021141},
+ {"eta",01667}, {"eth",0360}, {"euml",0353}, {"euro",020254},
+ {"exist",021003},{"fnof",0622}, {"forall",021000},{"frac12",0275},
+ {"frac14",0274},{"frac34",0276}, {"frasl",020104},{"gamma",01663},
+ {"ge",021145}, {"gt",62}, {"hArr",020724}, {"harr",020624},
+ {"hearts",023145},{"hellip",020046},{"iacute",0355},{"icirc",0356},
+ {"iexcl",0241}, {"igrave",0354}, {"image",020421},{"infin",021036},
+ {"int",021053}, {"iota",01671}, {"iquest",0277}, {"isin",021010},
+ {"iuml",0357}, {"kappa",01672}, {"lArr",020720}, {"lambda",01673},
+ {"lang",021451},{"laquo",0253}, {"larr",020620}, {"lceil",021410},
+ {"ldquo",020034},{"le",021144}, {"lfloor",021412},{"lowast",021027},
+ {"loz",022712}, {"lrm",020016}, {"lsaquo",020071},{"lsquo",020030},
+ {"lt",60}, {"macr",0257}, {"mdash",020024},{"micro",0265},
+ {"middot",0267},{"minus",021022},{"mu",01674}, {"nabla",021007},
+ {"nbsp",32}, {"ndash",020023},{"ne",021140}, {"ni",021013},
+ {"not",0254}, {"notin",021011},{"nsub",021204}, {"ntilde",0361},
+ {"nu",01675}, {"oacute",0363}, {"ocirc",0364}, {"oelig",0523},
+ {"ograve",0362},{"oline",020076},{"omega",01711}, {"omicron",01677},
+ {"oplus",021225},{"or",021050}, {"ordf",0252}, {"ordm",0272},
+ {"oslash",0370},{"otilde",0365}, {"otimes",021227},{"ouml",0366},
+ {"para",0266}, {"part",021002}, {"permil",020060},{"perp",021245},
+ {"phi",01706}, {"pi",01700}, {"piv",01726}, {"plusmn",0261},
+ {"pound",0243}, {"prime",020062},{"prod",021017}, {"prop",021035},
+ {"psi",01710}, {"quot",34}, {"rArr",020722}, {"radic",021032},
+ {"rang",021452},{"raquo",0273}, {"rarr",020622}, {"rceil",021411},
+ {"rdquo",020035},{"real",020434},{"reg",0256}, {"rfloor",021413},
+ {"rho",01701}, {"rlm",020017}, {"rsaquo",020072},{"rsquo",020031},
+ {"sbquo",020032},{"scaron",0541},{"sdot",021305}, {"sect",0247},
+ {"shy",0255}, {"sigma",01703}, {"sigmaf",01702},{"sim",021074},
+ {"spades",023140},{"sub",021202},{"sube",021206}, {"sum",021021},
+ {"sup",021203}, {"sup1",0271}, {"sup2",0262}, {"sup3",0263},
+ {"supe",021207},{"szlig",0337}, {"tau",01704}, {"there4",021064},
+ {"theta",01670},{"thetasym",01721},{"thinsp",020011},{"thorn",0376},
+ {"tilde",01334},{"times",0327}, {"trade",020442},{"uArr",020721},
+ {"uacute",0372},{"uarr",020621}, {"ucirc",0373}, {"ugrave",0371},
+ {"uml",0250}, {"upsih",01722}, {"upsilon",01705},{"uuml",0374},
+ {"weierp",020430},{"xi",01676}, {"yacute",0375}, {"yen",0245},
+ {"yuml",0377}, {"zeta",01666}, {"zwj",020015}, {"zwnj",020014}
+};
+
+
+/*
+ * Comparison function for binary search
+ */
+static int Html_entity_comp(const void *a, const void *b)
+{
+ return strcmp(((Ent_t *)a)->entity, ((Ent_t *)b)->entity);
+}
+
+/*
+ * Binary search of 'key' in entity list
+ */
+static int Html_entity_search(char *key)
+{
+ Ent_t *res, EntKey;
+
+ EntKey.entity = key;
+ res = (Ent_t*) bsearch(&EntKey, Entities, NumEnt,
+ sizeof(Ent_t), Html_entity_comp);
+ if (res)
+ return (res - Entities);
+ return -1;
+}
+
+/*
+ * This is M$ non-standard "smart quotes" (w1252). Now even deprecated by them!
+ *
+ * SGML for HTML4.01 defines c >= 128 and c <= 159 as UNUSED.
+ * TODO: Probably I should remove this hack, and add a HTML warning. --Jcid
+ */
+static int Html_ms_stupid_quotes_2ucs(int isocode)
+{
+ int ret;
+ switch (isocode) {
+ case 145:
+ case 146: ret = '\''; break;
+ case 147:
+ case 148: ret = '"'; break;
+ case 149: ret = 176; break;
+ case 150:
+ case 151: ret = '-'; break;
+ default: ret = isocode; break;
+ }
+ return ret;
+}
+
+/*
+ * Given an entity, return the UCS character code.
+ * Returns a negative value (error code) if not a valid entity.
+ *
+ * The first character *token is assumed to be == '&'
+ *
+ * For valid entities, *entsize is set to the length of the parsed entity.
+ */
+static int Html_parse_entity(DilloHtml *html, const char *token,
+ int toksize, int *entsize)
+{
+ int isocode, i;
+ char *tok, *s, c;
+
+ token++;
+ tok = s = toksize ? dStrndup(token, (uint_t)toksize) : dStrdup(token);
+
+ isocode = -1;
+
+ if (*s == '#') {
+ /* numeric character reference */
+ errno = 0;
+ if (*++s == 'x' || *s == 'X') {
+ if (isxdigit(*++s)) {
+ /* strtol with base 16 accepts leading "0x" - we don't */
+ if (*s == '0' && s[1] == 'x') {
+ s++;
+ isocode = 0;
+ } else {
+ isocode = strtol(s, &s, 16);
+ }
+ }
+ } else if (isdigit(*s)) {
+ isocode = strtol(s, &s, 10);
+ }
+
+ if (!isocode || errno || isocode > 0xffff) {
+ /* this catches null bytes, errors and codes >= 0xFFFF */
+ MSG_HTML("numeric character reference out of range\n");
+ isocode = -2;
+ }
+
+ if (isocode != -1) {
+ if (*s == ';')
+ s++;
+ else if (prefs.show_extra_warnings)
+ MSG_HTML("numeric character reference without trailing ';'\n");
+ }
+
+ } else if (isalpha(*s)) {
+ /* character entity reference */
+ while (isalnum(*++s) || strchr(":_.-", *s));
+ c = *s;
+ *s = 0;
+
+ if ((i = Html_entity_search(tok)) == -1) {
+ if ((html->DocType == DT_HTML && html->DocTypeVersion == 4.01f) ||
+ html->DocType == DT_XHTML)
+ MSG_HTML("undefined character entity '%s'\n", tok);
+ isocode = -3;
+ } else
+ isocode = Entities[i].isocode;
+
+ if (c == ';')
+ s++;
+ else if (prefs.show_extra_warnings)
+ MSG_HTML("character entity reference without trailing ';'\n");
+ }
+
+ *entsize = s-tok+1;
+ dFree(tok);
+
+ if (isocode >= 145 && isocode <= 151) {
+ /* TODO: remove this hack. */
+ isocode = Html_ms_stupid_quotes_2ucs(isocode);
+ } else if (isocode == -1 && prefs.show_extra_warnings)
+ MSG_HTML("literal '&'\n");
+
+ return isocode;
+}
+
+/*
+ * Convert all the entities in a token to utf8 encoding. Takes
+ * a token and its length, and returns a newly allocated string.
+ */
+static char *
+ Html_parse_entities(DilloHtml *html, char *token, int toksize)
+{
+ char *esc_set = "&\xE2\xC2";
+ char *new_str, buf[4];
+ int i, j, k, n, isocode, entsize;
+
+ new_str = dStrndup(token, toksize);
+ if (new_str[strcspn(new_str, esc_set)] == 0)
+ return new_str;
+
+ for (i = j = 0; i < toksize; i++) {
+ if (token[i] == '&' &&
+ (isocode = Html_parse_entity(html, token+i,
+ toksize-i, &entsize)) >= 0) {
+ if (isocode >= 128) {
+ /* multibyte encoding */
+ n = utf8encode(isocode, buf);
+ for (k = 0; k < n; ++k)
+ new_str[j++] = buf[k];
+ } else {
+ new_str[j++] = (char) isocode;
+ }
+ i += entsize-1;
+ } else {
+ new_str[j++] = token[i];
+ }
+ }
+ new_str[j] = '\0';
+ return new_str;
+}
+
+/*
+ * Parse spaces
+ */
+static void Html_process_space(DilloHtml *html, char *space, int spacesize)
+{
+ int i, offset;
+ DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode;
+
+ if (parse_mode == DILLO_HTML_PARSE_MODE_STASH) {
+ html->StashSpace = (html->Stash->len > 0);
+ html->SPCPending = FALSE;
+
+ } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {
+ char *Pword = dStrndup(space, spacesize);
+ dStr_append(html->Stash, Pword);
+ dFree(Pword);
+ html->SPCPending = FALSE;
+
+ } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) {
+ /* re-scan the string for characters that cause line breaks */
+ for (i = 0; i < spacesize; i++) {
+ /* Support for "\r", "\n" and "\r\n" line breaks (skips the first) */
+ if (!html->PreFirstChar &&
+ (space[i] == '\r' || (space[i] == '\n' && !html->PrevWasCR))) {
+ DW2TB(html->dw)->addLinebreak (S_TOP(html)->style);
+ html->pre_column = 0;
+ }
+ html->PreFirstChar = FALSE;
+
+ /* cr and lf should not be rendered -- they appear as a break */
+ switch (space[i]) {
+ case '\r':
+ case '\n':
+ break;
+ case '\t':
+ if (prefs.show_extra_warnings)
+ MSG_HTML("TAB character inside <PRE>\n");
+ offset = TAB_SIZE - html->pre_column % TAB_SIZE;
+ DW2TB(html->dw)->addText (dStrnfill(offset, ' '),
+ S_TOP(html)->style);
+ html->pre_column += offset;
+ break;
+ default:
+ DW2TB(html->dw)->addText (dStrndup(space + i, 1),
+ S_TOP(html)->style);
+ html->pre_column++;
+ break;
+ }
+
+ html->PrevWasCR = (space[i] == '\r');
+ }
+ html->SPCPending = FALSE;
+
+ } else {
+ if (SGML_SPCDEL && html->PrevWasOpenTag) {
+ /* SGML_SPCDEL ignores white space inmediately after an open tag */
+ html->SPCPending = FALSE;
+ } else {
+ dFree(html->SPCBuf);
+ html->SPCBuf = dStrndup(space, spacesize);
+ html->SPCPending = TRUE;
+ }
+
+ if (parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY)
+ html->StashSpace = (html->Stash->len > 0);
+ }
+}
+
+/*
+ * Handles putting the word into its proper place
+ * > STASH and VERBATIM --> html->Stash
+ * > otherwise it goes through addText()
+ *
+ * Entities are parsed (or not) according to parse_mode.
+ */
+static void Html_process_word(DilloHtml *html, char *word, int size)
+{
+ int i, j, start;
+ char *Pword;
+ DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode;
+
+ if (parse_mode == DILLO_HTML_PARSE_MODE_STASH ||
+ parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY) {
+ if (html->StashSpace) {
+ dStr_append_c(html->Stash, ' ');
+ html->StashSpace = FALSE;
+ }
+ Pword = Html_parse_entities(html, word, size);
+ dStr_append(html->Stash, Pword);
+ dFree(Pword);
+
+ } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {
+ /* word goes in untouched, it is not processed here. */
+ Pword = dStrndup(word, size);
+ dStr_append(html->Stash, Pword);
+ dFree(Pword);
+ }
+
+ if (parse_mode == DILLO_HTML_PARSE_MODE_STASH ||
+ parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {
+ /* skip until the closing instructions */
+
+ } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) {
+ /* all this overhead is to catch white-space entities */
+ Pword = Html_parse_entities(html, word, size);
+ for (start = i = 0; Pword[i]; start = i)
+ if (isspace(Pword[i])) {
+ while (Pword[++i] && isspace(Pword[i]));
+ Html_process_space(html, Pword + start, i - start);
+ } else {
+ while (Pword[++i] && !isspace(Pword[i]));
+ DW2TB(html->dw)->addText(
+ dStrndup(Pword + start, i - start),
+ S_TOP(html)->style);
+ html->pre_column += i - start;
+ html->PreFirstChar = FALSE;
+ }
+ dFree(Pword);
+
+ } else {
+ /* add pending space if present */
+ if (html->SPCPending && (!SGML_SPCDEL || !html->PrevWasOpenTag))
+ /* SGML_SPCDEL ignores space after an open tag */
+ DW2TB(html->dw)->addSpace (S_TOP(html)->style);
+
+ /* Collapse white-space entities inside the word (except &nbsp;) */
+ Pword = Html_parse_entities(html, word, size);
+ for (i = 0; Pword[i]; ++i)
+ if (strchr("\t\f\n\r", Pword[i]))
+ for (j = i; (Pword[j] = Pword[j+1]); ++j);
+
+ DW2TB(html->dw)->addText(Pword, S_TOP(html)->style);
+ }
+
+ html->PrevWasOpenTag = FALSE;
+ html->SPCPending = FALSE;
+}
+
+/*
+ * Does the tag in tagstr (e.g. "p") match the tag in the tag, tagsize
+ * structure, with the initial < skipped over (e.g. "P align=center>")
+ */
+static bool_t Html_match_tag(const char *tagstr, char *tag, int tagsize)
+{
+ int i;
+
+ for (i = 0; i < tagsize && tagstr[i] != '\0'; i++) {
+ if (tolower(tagstr[i]) != tolower(tag[i]))
+ return FALSE;
+ }
+ /* The test for '/' is for xml compatibility: "empty/>" will be matched. */
+ if (i < tagsize && (isspace(tag[i]) || tag[i] == '>' || tag[i] == '/'))
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * This function is called after popping the stack, to
+ * handle nested DwPage widgets.
+ */
+static void Html_eventually_pop_dw(DilloHtml *html, bool_t hand_over_break)
+{
+ if (html->dw != S_TOP(html)->textblock) {
+ if (hand_over_break)
+ DW2TB(html->dw)->handOverBreak (S_TOP(html)->style);
+ DW2TB(html->dw)->flush ();
+ html->dw = S_TOP(html)->textblock;
+ }
+}
+
+/*
+ * Push the tag (copying attributes from the top of the stack)
+ */
+static void Html_push_tag(DilloHtml *html, int tag_idx)
+{
+ char *tagstr;
+ int n_items;
+
+ /* Save the element's name (no parameters) into tagstr. */
+ tagstr = dStrdup(Tags[tag_idx].name);
+
+ n_items = html->stack->size ();
+ html->stack->increase ();
+ /* We'll copy the former stack item and just change the tag and its index
+ * instead of copying all fields except for tag. --Jcid */
+ *html->stack->getRef(n_items) = *html->stack->getRef(n_items - 1);
+ html->stack->getRef(n_items)->tag_name = tagstr;
+ html->stack->getRef(n_items)->tag_idx = tag_idx;
+ /* proper memory management, may be unref'd later */
+ (S_TOP(html)->style)->ref ();
+ if (S_TOP(html)->table_cell_style)
+ (S_TOP(html)->table_cell_style)->ref ();
+ html->dw = S_TOP(html)->textblock;
+}
+
+/*
+ * Push the tag (used to force en element with optional open into the stack)
+ * Note: now it's the same as Html_push_tag(), but things may change...
+ */
+static void Html_force_push_tag(DilloHtml *html, int tag_idx)
+{
+ Html_push_tag(html, tag_idx);
+}
+
+/*
+ * Pop the top tag in the stack
+ */
+static void Html_real_pop_tag(DilloHtml *html)
+{
+ bool_t hand_over_break;
+
+ (S_TOP(html)->style)->unref ();
+ if (S_TOP(html)->table_cell_style)
+ (S_TOP(html)->table_cell_style)->unref ();
+ dFree(S_TOP(html)->tag_name);
+ hand_over_break = S_TOP(html)->hand_over_break;
+ html->stack->setSize (html->stack->size() - 1);
+ Html_eventually_pop_dw(html, hand_over_break);
+}
+
+/*
+ * Default close function for tags.
+ * (conditional cleanup of the stack)
+ * There're several ways of doing it. Considering the HTML 4.01 spec
+ * which defines optional close tags, and the will to deliver useful diagnose
+ * messages for bad-formed HTML, it'll go as follows:
+ * 1.- Search the stack for the first tag that requires a close tag.
+ * 2.- If it matches, clean all the optional-close tags in between.
+ * 3.- Cleanup the matching tag. (on error, give a warning message)
+ *
+ * If 'w3c_mode' is NOT enabled:
+ * 1.- Search the stack for a matching tag based on tag level.
+ * 2.- If it exists, clean all the tags in between.
+ * 3.- Cleanup the matching tag. (on error, give a warning message)
+ */
+static void Html_tag_cleanup_at_close(DilloHtml *html, int TagIdx)
+{
+ int w3c_mode = !prefs.w3c_plus_heuristics;
+ int stack_idx, cmp = 1;
+ int new_idx = TagIdx;
+
+ if (html->CloseOneTag) {
+ Html_real_pop_tag(html);
+ html->CloseOneTag = FALSE;
+ return;
+ }
+
+ /* Look for the candidate tag to close */
+ stack_idx = html->stack->size() - 1;
+ while (stack_idx &&
+ (cmp = (new_idx != html->stack->getRef(stack_idx)->tag_idx)) &&
+ ((w3c_mode &&
+ Tags[html->stack->getRef(stack_idx)->tag_idx].EndTag == 'O') ||
+ (!w3c_mode &&
+ Tags[html->stack->getRef(stack_idx)->tag_idx].TagLevel <
+ Tags[new_idx].TagLevel))) {
+ --stack_idx;
+ }
+
+ /* clean, up to the matching tag */
+ if (cmp == 0 && stack_idx > 0) {
+ /* There's a valid matching tag in the stack */
+ while (html->stack->size() > stack_idx) {
+ int toptag_idx = S_TOP(html)->tag_idx;
+ /* Warn when we decide to close an open tag (for !w3c_mode) */
+ if (html->stack->size() > stack_idx + 1 &&
+ Tags[toptag_idx].EndTag != 'O')
+ MSG_HTML(" - forcing close of open tag: <%s>\n",
+ Tags[toptag_idx].name);
+
+ /* Close this and only this tag */
+ html->CloseOneTag = TRUE;
+ Tags[toptag_idx].close (html, toptag_idx);
+ }
+
+ } else {
+ MSG_HTML("unexpected closing tag: </%s>. -- expected </%s>\n",
+ Tags[new_idx].name, html->stack->getRef(stack_idx)->tag_name);
+ }
+}
+
+/*
+ * Cleanup (conditional), and Pop the tag (if it matches)
+ */
+static void Html_pop_tag(DilloHtml *html, int TagIdx)
+{
+ Html_tag_cleanup_at_close(html, TagIdx);
+}
+
+/*
+ * Some parsing routines.
+ */
+
+/*
+ * Used by Html_parse_length
+ */
+static Length Html_parse_length_or_multi_length (const char *attr,
+ char **endptr)
+{
+ Length l;
+ double v;
+ char *end;
+
+ v = strtod (attr, &end);
+ switch (*end) {
+ case '%':
+ end++;
+ l = createPerLength (v / 100);
+ break;
+
+ case '*':
+ end++;
+ l = createRelLength (v);
+ break;
+/*
+ The "px" suffix seems not allowed by HTML4.01 SPEC.
+ case 'p':
+ if (end[1] == 'x')
+ end += 2;
+*/
+ default:
+ l = createAbsLength ((int)v);
+ break;
+ }
+
+ if (endptr)
+ *endptr = end;
+ return l;
+}
+
+
+/*
+ * Returns a length or a percentage, or UNDEF_LENGTH in case
+ * of an error, or if attr is NULL.
+ */
+static Length Html_parse_length (DilloHtml *html, const char *attr)
+{
+ Length l;
+ char *end;
+
+ l = Html_parse_length_or_multi_length (attr, &end);
+ if (isRelLength (l))
+ /* not allowed as &Length; */
+ return LENGTH_AUTO;
+ else {
+ /* allow only whitespaces */
+ if (*end && !isspace (*end)) {
+ MSG_HTML("Garbage after length: %s\n", attr);
+ return LENGTH_AUTO;
+ }
+ }
+
+ _MSG("Html_parse_length: \"%s\" %d\n", attr, absLengthVal(l));
+ return l;
+}
+
+/*
+ * Parse a color attribute.
+ * Return value: parsed color, or default_color (+ error msg) on error.
+ */
+static int32_t
+ Html_color_parse(DilloHtml *html, const char *subtag, int32_t default_color)
+{
+ int err = 1;
+ int32_t color = a_Color_parse(subtag, default_color, &err);
+
+ if (err) {
+ MSG_HTML("color is not in \"#RRGGBB\" format\n");
+ }
+ return color;
+}
+
+/*
+ * Check that 'val' is composed of characters inside [A-Za-z0-9:_.-]
+ * Note: ID can't have entities, but this check is enough (no '&').
+ * Return value: 1 if OK, 0 otherwise.
+ */
+static int
+ Html_check_name_val(DilloHtml *html, const char *val, const char *attrname)
+{
+ int i;
+
+ for (i = 0; val[i]; ++i)
+ if (!(isalnum(val[i]) || strchr(":_.-", val[i])))
+ break;
+
+ if (val[i] || !isalpha(val[0]))
+ MSG_HTML("'%s' value is not of the form "
+ "[A-Za-z][A-Za-z0-9:_.-]*\n", attrname);
+
+ return !(val[i]);
+}
+
+/*
+ * Handle DOCTYPE declaration
+ *
+ * Follows the convention that HTML 4.01
+ * doctypes which include a full w3c DTD url are treated as
+ * standards-compliant, but 4.01 without the url and HTML 4.0 and
+ * earlier are not. XHTML doctypes are always standards-compliant
+ * whether or not an url is present.
+ *
+ * Note: I'm not sure about this convention. The W3C validator
+ * recognizes the "HTML Level" with or without the URL. The convention
+ * comes from mozilla (see URLs below), but Dillo doesn't have the same
+ * rendering modes, so it may be better to chose another behaviour. --Jcid
+ *
+ * http://www.mozilla.org/docs/web-developer/quirks/doctypes.html
+ * http://lists.auriga.wearlab.de/pipermail/dillo-dev/2004-October/002300.html
+ *
+ * This is not a full DOCTYPE parser, just enough for what Dillo uses.
+ */
+static void Html_parse_doctype(DilloHtml *html, char *tag, int tagsize)
+{
+ char *HTML_sig = "<!DOCTYPE HTML PUBLIC ";
+ char *HTML20 = "-//IETF//DTD HTML//EN";
+ char *HTML32 = "-//W3C//DTD HTML 3.2";
+ char *HTML40 = "-//W3C//DTD HTML 4.0";
+ char *HTML401 = "-//W3C//DTD HTML 4.01";
+ char *HTML401_url = "http://www.w3.org/TR/html4/";
+ char *XHTML1 = "-//W3C//DTD XHTML 1.0";
+ char *XHTML1_url = "http://www.w3.org/TR/xhtml1/DTD/";
+ char *XHTML11 = "-//W3C//DTD XHTML 1.1";
+ char *XHTML11_url = "http://www.w3.org/TR/xhtml11/DTD/";
+
+ int i, quote;
+ char *p, *ntag = dStrndup(tag, tagsize);
+
+ /* Tag sanitization: Collapse whitespace between tokens
+ * and replace '\n' and '\r' with ' ' inside quoted strings. */
+ for (i = 0, p = ntag; *p; ++p) {
+ if (isspace(*p)) {
+ for (ntag[i++] = ' '; isspace(p[1]); ++p);
+ } else if ((quote = *p) == '"' || *p == '\'') {
+ for (ntag[i++] = *p++; (ntag[i++] = *p) && *p != quote; ++p) {
+ if (*p == '\n' || *p == '\r')
+ ntag[i - 1] = ' ';
+ p += (p[0] == '\r' && p[1] == '\n') ? 1 : 0;
+ }
+ } else {
+ ntag[i++] = *p;
+ }
+ if (!*p)
+ break;
+ }
+ ntag[i] = 0;
+
+ _MSG("New: {%s}\n", ntag);
+
+ /* The default DT_NONE type is TagSoup */
+ if (!dStrncasecmp(ntag, HTML_sig, strlen(HTML_sig))) {
+ p = ntag + strlen(HTML_sig) + 1;
+ if (!strncmp(p, HTML401, strlen(HTML401)) &&
+ dStristr(p + strlen(HTML401), HTML401_url)) {
+ html->DocType = DT_HTML;
+ html->DocTypeVersion = 4.01f;
+ } else if (!strncmp(p, XHTML1, strlen(XHTML1)) &&
+ dStristr(p + strlen(XHTML1), XHTML1_url)) {
+ html->DocType = DT_XHTML;
+ html->DocTypeVersion = 1.0f;
+ } else if (!strncmp(p, XHTML11, strlen(XHTML11)) &&
+ dStristr(p + strlen(XHTML11), XHTML11_url)) {
+ html->DocType = DT_XHTML;
+ html->DocTypeVersion = 1.1f;
+ } else if (!strncmp(p, HTML40, strlen(HTML40))) {
+ html->DocType = DT_HTML;
+ html->DocTypeVersion = 4.0f;
+ } else if (!strncmp(p, HTML32, strlen(HTML32))) {
+ html->DocType = DT_HTML;
+ html->DocTypeVersion = 3.2f;
+ } else if (!strncmp(p, HTML20, strlen(HTML20))) {
+ html->DocType = DT_HTML;
+ html->DocTypeVersion = 2.0f;
+ }
+ }
+
+ dFree(ntag);
+}
+
+/*
+ * Handle open HTML element
+ */
+static void Html_tag_open_html(DilloHtml *html, char *tag, int tagsize)
+{
+ if (!(html->InFlags & IN_HTML))
+ html->InFlags |= IN_HTML;
+ ++html->Num_HTML;
+
+ if (html->Num_HTML > 1) {
+ MSG_HTML("HTML element was already open\n");
+ }
+}
+
+/*
+ * Handle close HTML element
+ */
+static void Html_tag_close_html(DilloHtml *html, int TagIdx)
+{
+ /* todo: may add some checks here */
+ if (html->Num_HTML == 1) {
+ /* beware of pages with multiple HTML close tags... :-P */
+ html->InFlags &= ~IN_HTML;
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle open HEAD element
+ */
+static void Html_tag_open_head(DilloHtml *html, char *tag, int tagsize)
+{
+ if (html->InFlags & IN_BODY) {
+ MSG_HTML("HEAD element must go before the BODY section\n");
+ html->ReqTagClose = TRUE;
+ return;
+ }
+
+ if (!(html->InFlags & IN_HEAD))
+ html->InFlags |= IN_HEAD;
+ ++html->Num_HEAD;
+
+ if (html->Num_HEAD > 1) {
+ MSG_HTML("HEAD element was already open\n");
+ }
+}
+
+/*
+ * Handle close HEAD element
+ * Note: as a side effect of Html_test_section() this function is called
+ * twice when the head element is closed implicitly.
+ */
+static void Html_tag_close_head(DilloHtml *html, int TagIdx)
+{
+ if (html->InFlags & IN_HEAD) {
+ if (html->Num_TITLE == 0)
+ MSG_HTML("HEAD section lacks the TITLE element\n");
+
+ html->InFlags &= ~IN_HEAD;
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle open TITLE
+ * calls stash init, where the title string will be stored
+ */
+static void Html_tag_open_title(DilloHtml *html, char *tag, int tagsize)
+{
+ ++html->Num_TITLE;
+ Html_stash_init(html);
+}
+
+/*
+ * Handle close TITLE
+ * set page-title in the browser window and in the history.
+ */
+static void Html_tag_close_title(DilloHtml *html, int TagIdx)
+{
+ if (html->InFlags & IN_HEAD) {
+ /* title is only valid inside HEAD */
+ a_UIcmd_set_page_title(html->linkblock->bw, html->Stash->str);
+ a_History_set_title(NAV_TOP(html->linkblock->bw), html->Stash->str);
+ } else {
+ MSG_HTML("the TITLE element must be inside the HEAD section\n");
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle open SCRIPT
+ * initializes stash, where the embedded code will be stored.
+ * MODE_VERBATIM is used because MODE_STASH catches entities.
+ */
+static void Html_tag_open_script(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_stash_init(html);
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;
+}
+
+/*
+ * Handle close SCRIPT
+ */
+static void Html_tag_close_script(DilloHtml *html, int TagIdx)
+{
+ /* eventually the stash will be sent to an interpreter for parsing */
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle open STYLE
+ * store the contents to the stash where (in the future) the style
+ * sheet interpreter can get it.
+ */
+static void Html_tag_open_style(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_stash_init(html);
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;
+}
+
+/*
+ * Handle close STYLE
+ */
+static void Html_tag_close_style(DilloHtml *html, int TagIdx)
+{
+ /* eventually the stash will be sent to an interpreter for parsing */
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <BODY>
+ */
+static void Html_tag_open_body(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ Textblock *textblock;
+ StyleAttrs style_attrs;
+ Style *style;
+ int32_t color;
+
+ if (!(html->InFlags & IN_BODY))
+ html->InFlags |= IN_BODY;
+ ++html->Num_BODY;
+
+ if (html->Num_BODY > 1) {
+ MSG_HTML("BODY element was already open\n");
+ return;
+ }
+ if (html->InFlags & IN_HEAD) {
+ /* if we're here, it's bad XHTML, no need to recover */
+ MSG_HTML("unclosed HEAD element\n");
+ }
+
+ textblock = DW2TB(html->dw);
+
+ if (!prefs.force_my_colors) {
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) {
+ color = Html_color_parse(html, attrbuf, prefs.bg_color);
+ if ((color == 0xffffff && !prefs.allow_white_bg) ||
+ prefs.force_my_colors)
+ color = prefs.bg_color;
+
+ style_attrs = *html->dw->getStyle ();
+ style_attrs.backgroundColor =
+ Color::createSimple (HT2LT(html), color);
+ style = Style::create (HT2LT(html), &style_attrs);
+ html->dw->setStyle (style);
+ style->unref ();
+ S_TOP(html)->current_bg_color = color;
+ }
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "text"))) {
+ color = Html_color_parse(html, attrbuf, prefs.text_color);
+ HTML_SET_TOP_ATTR (html, color,
+ Color::createSimple (HT2LT(html),color));
+ }
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "link")))
+ html->linkblock->link_color = Html_color_parse(html, attrbuf,
+ prefs.link_color);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "vlink")))
+ html->linkblock->visited_color =
+ Html_color_parse(html, attrbuf, prefs.visited_color);
+
+ if (prefs.contrast_visited_color) {
+ /* get a color that has a "safe distance" from text, link and bg */
+ html->linkblock->visited_color =
+ a_Color_vc(html->linkblock->visited_color,
+ S_TOP(html)->style->color->getColor(),
+ html->linkblock->link_color,
+ S_TOP(html)->current_bg_color);
+ }
+ }
+
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_BODY;
+}
+
+/*
+ * BODY
+ */
+static void Html_tag_close_body(DilloHtml *html, int TagIdx)
+{
+ if (html->Num_BODY == 1) {
+ /* some tag soup pages use multiple BODY tags... */
+ html->InFlags &= ~IN_BODY;
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <P>
+ * todo: what's the point between adding the parbreak before and
+ * after the push?
+ */
+static void Html_tag_open_p(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_tag_set_align_attr (html, tag, tagsize);
+}
+
+/*
+ * <TABLE>
+ */
+static void Html_tag_open_table(DilloHtml *html, char *tag, int tagsize)
+{
+#ifdef USE_TABLES
+ Widget *table;
+ StyleAttrs style_attrs;
+ Style *tstyle, *old_style;
+ const char *attrbuf;
+ int32_t border = 0, cellspacing = 1, cellpadding = 2, bgcolor;
+#endif
+
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+
+#ifdef USE_TABLES
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "border")))
+ border = isdigit(attrbuf[0]) ? strtol (attrbuf, NULL, 10) : 1;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "cellspacing")))
+ cellspacing = strtol (attrbuf, NULL, 10);
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "cellpadding")))
+ cellpadding = strtol (attrbuf, NULL, 10);
+
+ /* The style for the table */
+ style_attrs = *S_TOP(html)->style;
+
+ /* When dillo was started with the --debug-rendering option, there
+ * is always a border around the table. */
+ if (dillo_dbg_rendering)
+ style_attrs.borderWidth.setVal (MIN (border, 1));
+ else
+ style_attrs.borderWidth.setVal (border);
+
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html),
+ S_TOP(html)->current_bg_color));
+ style_attrs.setBorderStyle (BORDER_OUTSET);
+ style_attrs.hBorderSpacing = cellspacing;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "width")))
+ style_attrs.width = Html_parse_length (html, attrbuf);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "align"))) {
+ if (dStrcasecmp (attrbuf, "left") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_LEFT;
+ else if (dStrcasecmp (attrbuf, "right") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_RIGHT;
+ else if (dStrcasecmp (attrbuf, "center") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_CENTER;
+ }
+
+ if (!prefs.force_my_colors &&
+ (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) {
+ bgcolor = Html_color_parse(html, attrbuf, -1);
+ if (bgcolor != -1) {
+ if (bgcolor == 0xffffff && !prefs.allow_white_bg)
+ bgcolor = prefs.bg_color;
+ S_TOP(html)->current_bg_color = bgcolor;
+ style_attrs.backgroundColor =
+ Color::createSimple (HT2LT(html), bgcolor);
+ }
+ }
+
+ tstyle = Style::create (HT2LT(html), &style_attrs);
+
+ /* The style for the cells */
+ style_attrs = *S_TOP(html)->style;
+ /* When dillo was started with the --debug-rendering option, there
+ * is always a border around the cells. */
+ if (dillo_dbg_rendering)
+ style_attrs.borderWidth.setVal (1);
+ else
+ style_attrs.borderWidth.setVal (border ? 1 : 0);
+
+ style_attrs.padding.setVal(cellpadding);
+ style_attrs.setBorderColor (tstyle->borderColor.top);
+ style_attrs.setBorderStyle (BORDER_INSET);
+
+ old_style = S_TOP(html)->table_cell_style;
+ S_TOP(html)->table_cell_style =
+ Style::create (HT2LT(html), &style_attrs);
+ if (old_style)
+ old_style->unref ();
+
+ table = new Table(false);
+ DW2TB(html->dw)->addWidget (table, tstyle);
+ tstyle->unref ();
+
+ S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TOP;
+ S_TOP(html)->cell_text_align_set = FALSE;
+ S_TOP(html)->table = table;
+#endif
+}
+
+
+/*
+ * used by <TD> and <TH>
+ */
+static void Html_tag_open_table_cell(DilloHtml *html, char *tag, int tagsize,
+ TextAlignType text_align)
+{
+#ifdef USE_TABLES
+ Widget *col_tb;
+ int colspan = 1, rowspan = 1;
+ const char *attrbuf;
+ StyleAttrs style_attrs;
+ Style *style, *old_style;
+ int32_t bgcolor;
+ bool_t new_style;
+
+ switch (S_TOP(html)->table_mode) {
+ case DILLO_HTML_TABLE_MODE_NONE:
+ MSG_HTML("<td> or <th> outside <table>\n");
+ return;
+
+ case DILLO_HTML_TABLE_MODE_TOP:
+ MSG_HTML("<td> or <th> outside <tr>\n");
+ /* a_Dw_table_add_cell takes care that dillo does not crash. */
+ /* continues */
+ case DILLO_HTML_TABLE_MODE_TR:
+ case DILLO_HTML_TABLE_MODE_TD:
+ /* todo: check errors? */
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "colspan")))
+ colspan = strtol (attrbuf, NULL, 10);
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "rowspan")))
+ rowspan = strtol (attrbuf, NULL, 10);
+
+ /* text style */
+ old_style = S_TOP(html)->style;
+ style_attrs = *old_style;
+ if (!S_TOP(html)->cell_text_align_set)
+ style_attrs.textAlign = text_align;
+ if (Html_get_attr(html, tag, tagsize, "nowrap"))
+ style_attrs.whiteSpace = WHITE_SPACE_NOWRAP;
+ else
+ style_attrs.whiteSpace = WHITE_SPACE_NORMAL;
+
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ old_style->unref ();
+ Html_tag_set_align_attr (html, tag, tagsize);
+
+ /* cell style */
+ style_attrs = *S_TOP(html)->table_cell_style;
+ new_style = FALSE;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "width"))) {
+ style_attrs.width = Html_parse_length (html, attrbuf);
+ new_style = TRUE;
+ }
+
+ if (Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs))
+ new_style = TRUE;
+
+ if (!prefs.force_my_colors &&
+ (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) {
+ bgcolor = Html_color_parse(html, attrbuf, -1);
+ if (bgcolor != -1) {
+ if (bgcolor == 0xffffff && !prefs.allow_white_bg)
+ bgcolor = prefs.bg_color;
+
+ new_style = TRUE;
+ style_attrs.backgroundColor =
+ Color::createSimple (HT2LT(html), bgcolor);
+ S_TOP(html)->current_bg_color = bgcolor;
+ }
+ }
+
+ if (S_TOP(html)->style->textAlign
+ == TEXT_ALIGN_STRING)
+ col_tb = new TableCell (
+ ((Table*)S_TOP(html)->table)->getCellRef (),
+ false);
+ else
+ col_tb = new Textblock (false);
+
+ if (new_style) {
+ style = Style::create (HT2LT(html), &style_attrs);
+ col_tb->setStyle (style);
+ style->unref ();
+ } else
+ col_tb->setStyle (S_TOP(html)->table_cell_style);
+
+ ((Table*)S_TOP(html)->table)->addCell (
+ col_tb, colspan, rowspan);
+ S_TOP(html)->textblock = html->dw = col_tb;
+
+ /* Handle it when the user clicks on a link */
+ Html_connect_signals(html, col_tb);
+ break;
+
+ default:
+ /* compiler happiness */
+ break;
+ }
+
+ S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TD;
+#endif
+}
+
+
+/*
+ * <TD>
+ */
+static void Html_tag_open_td(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_LEFT);
+}
+
+
+/*
+ * <TH>
+ */
+static void Html_tag_open_th(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 1, 1);
+ Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_CENTER);
+}
+
+
+/*
+ * <TR>
+ */
+static void Html_tag_open_tr(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ StyleAttrs style_attrs;
+ Style *style, *old_style;
+ int32_t bgcolor;
+
+#ifdef USE_TABLES
+ switch (S_TOP(html)->table_mode) {
+ case DILLO_HTML_TABLE_MODE_NONE:
+ _MSG("Invalid HTML syntax: <tr> outside <table>\n");
+ return;
+
+ case DILLO_HTML_TABLE_MODE_TOP:
+ case DILLO_HTML_TABLE_MODE_TR:
+ case DILLO_HTML_TABLE_MODE_TD:
+ style = NULL;
+
+ if (!prefs.force_my_colors &&
+ (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) {
+ bgcolor = Html_color_parse(html, attrbuf, -1);
+ if (bgcolor != -1) {
+ if (bgcolor == 0xffffff && !prefs.allow_white_bg)
+ bgcolor = prefs.bg_color;
+
+ style_attrs = *S_TOP(html)->style;
+ style_attrs.backgroundColor =
+ Color::createSimple (HT2LT(html), bgcolor);
+ style = Style::create (HT2LT(html), &style_attrs);
+ S_TOP(html)->current_bg_color = bgcolor;
+ }
+ }
+
+ ((Table*)S_TOP(html)->table)->addRow (style);
+ if (style)
+ style->unref ();
+
+ if (Html_get_attr (html, tag, tagsize, "align")) {
+ S_TOP(html)->cell_text_align_set = TRUE;
+ Html_tag_set_align_attr (html, tag, tagsize);
+ }
+
+ style_attrs = *S_TOP(html)->table_cell_style;
+ if (Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs)) {
+ old_style = S_TOP(html)->table_cell_style;
+ S_TOP(html)->table_cell_style =
+ Style::create (HT2LT(html), &style_attrs);
+ old_style->unref ();
+ } else
+
+ break;
+
+ default:
+ break;
+ }
+
+ S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TR;
+#else
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+#endif
+}
+
+/*
+ * <FRAME>, <IFRAME>
+ * todo: This is just a temporary fix while real frame support
+ * isn't finished. Imitates lynx/w3m's frames.
+ */
+static void Html_tag_open_frame (DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ char *src, *buf;
+ DilloUrl *url;
+ Textblock *textblock;
+ StyleAttrs style_attrs;
+ Style *link_style;
+ Widget *bullet;
+ int buf_size;
+
+ textblock = DW2TB(html->dw);
+
+ if (!(attrbuf = Html_get_attr(html, tag, tagsize, "src")))
+ return;
+
+ if (!(url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0)))
+ return;
+
+ src = dStrdup(attrbuf);
+
+ style_attrs = *(S_TOP(html)->style);
+
+ if (a_Capi_get_buf(url, &buf, &buf_size)) /* visited frame */
+ style_attrs.color =
+ Color::createSimple (HT2LT(html),
+ html->linkblock->visited_color);
+ else /* unvisited frame */
+ style_attrs.color = Color::createSimple (HT2LT(html),
+ html->linkblock->link_color);
+
+ style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE;
+ style_attrs.x_link = Html_set_new_link(html, &url);
+ link_style = Style::create (HT2LT(html), &style_attrs);
+
+ textblock->addParbreak (5, S_TOP(html)->style);
+
+ /* The bullet will be assigned the current list style, which should
+ * be "disc" by default, but may in very weird pages be different.
+ * Anyway, there should be no harm. */
+ bullet = new Bullet();
+ textblock->addWidget(bullet, S_TOP(html)->style);
+ textblock->addSpace(S_TOP(html)->style);
+
+ if (tolower(tag[1]) == 'i') {
+ /* IFRAME usually comes with very long advertising/spying URLS,
+ * to not break rendering we will force name="IFRAME" */
+ textblock->addText (dStrdup("IFRAME"), link_style);
+
+ } else {
+ /* FRAME:
+ * If 'name' tag is present use it, if not use 'src' value */
+ if (!(attrbuf = Html_get_attr(html, tag, tagsize, "name"))) {
+ textblock->addText (dStrdup(src), link_style);
+ } else {
+ textblock->addText (dStrdup(attrbuf), link_style);
+ }
+ }
+
+ textblock->addParbreak (5, S_TOP(html)->style);
+
+ link_style->unref ();
+ dFree(src);
+}
+
+/*
+ * <FRAMESET>
+ * todo: This is just a temporary fix while real frame support
+ * isn't finished. Imitates lynx/w3m's frames.
+ */
+static void Html_tag_open_frameset (DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ DW2TB(html->dw)->addText(dStrdup("--FRAME--"),
+ S_TOP(html)->style);
+ Html_add_indented(html, 40, 0, 5);
+}
+
+/*
+ * <H1> | <H2> | <H3> | <H4> | <H5> | <H6>
+ */
+static void Html_tag_open_h(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+
+ /* todo: combining these two would be slightly faster */
+ Html_set_top_font(html, prefs.vw_fontname,
+ Html_level_to_fontsize(FontSizesNum - (tag[2] - '0')),
+ 1, 3);
+ Html_tag_set_align_attr (html, tag, tagsize);
+
+ /* First finalize unclosed H tags (we test if already named anyway) */
+ a_Menu_pagemarks_set_text(html->bw, html->Stash->str);
+ a_Menu_pagemarks_add(html->bw, DW2TB(html->dw),
+ S_TOP(html)->style, (tag[2] - '0'));
+ Html_stash_init(html);
+ S_TOP(html)->parse_mode =
+ DILLO_HTML_PARSE_MODE_STASH_AND_BODY;
+}
+
+/*
+ * Handle close: <H1> | <H2> | <H3> | <H4> | <H5> | <H6>
+ */
+static void Html_tag_close_h(DilloHtml *html, int TagIdx)
+{
+ a_Menu_pagemarks_set_text(html->bw, html->Stash->str);
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <BIG> | <SMALL>
+ */
+static void Html_tag_open_big_small(DilloHtml *html, char *tag, int tagsize)
+{
+ int level;
+
+ level =
+ Html_fontsize_to_level(S_TOP(html)->style->font->size) +
+ ((dStrncasecmp(tag+1, "big", 3)) ? -1 : 1);
+ Html_set_top_font(html, NULL, Html_level_to_fontsize(level), 0, 0);
+}
+
+/*
+ * <BR>
+ */
+static void Html_tag_open_br(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addLinebreak (S_TOP(html)->style);
+}
+
+/*
+ * <BUTTON>
+ */
+static void Html_tag_open_button(DilloHtml *html, char *tag, int tagsize)
+{
+// // AL
+// /*
+// * Buttons are rendered on one line, this is (at several levels) a
+// * bit simpler. May be changed in the future.
+// */
+// StyleAttrs style_attrs;
+// Style *style;
+// Widget *button, *page;
+// DilloHtmlForm *form;
+// DilloHtmlLB *html_lb;
+// DilloHtmlInputType inp_type;
+// char *name, *value, *type;
+//
+// /* Render the button */
+// style_attrs = *S_TOP(html)->style;
+//
+// style_attrs.margin.setVal (0);
+// style_attrs.borderWidth.setVal (0);
+// style_attrs.padding.setVal(0);
+// style = Style::create (HT2LT(html), &style_attrs);
+// button = a_Dw_button_new (DW_USES_HINTS, TRUE);
+//
+// /* The new button is not set button-insensitive, since nested buttons
+// * (if they are anyway allowed, todo: search in spec) should all be
+// * activatable. */
+// a_Dw_widget_set_button_sensitive (button, TRUE);
+//
+// DW2TB(html->dw)->addParbreak (5, style);
+// DW2TB(html->dw)->addWidget (button, style);
+// DW2TB(html->dw)->addParbreak (5, style);
+// style->unref ();
+//
+// style_attrs.margin.setVal (5);
+// style = Style::create (HT2LT(html), &style_attrs);
+// page = new Textblock (false);
+// page->setStyle (style);
+// style->unref ();
+// a_Dw_container_add (DW_CONTAINER (button), page);
+// a_Dw_widget_set_button_sensitive (DW_WIDGET (page), FALSE);
+// style_attrs.margin.setVal (0);
+//
+// a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE);
+//
+// S_TOP(html)->textblock = html->dw = page;
+//
+// /* Handle it when the user clicks on a link */
+// Html_connect_signals(html, GTK_OBJECT(page));
+//
+// /* Connect it to the form */
+// html_lb = html->linkblock;
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+//
+// type = Html_get_attr_wdef(html, tag, tagsize, "type", "");
+//
+// if (strcmp(type, "submit") == 0) {
+// inp_type = DILLO_HTML_INPUT_BUTTON_SUBMIT;
+// gtk_signal_connect(GTK_OBJECT(button), "clicked",
+// GTK_SIGNAL_FUNC(Html_submit_form), html_lb);
+// } else if (strcmp(type, "reset") == 0) {
+// inp_type = DILLO_HTML_INPUT_BUTTON_RESET;
+// gtk_signal_connect(GTK_OBJECT(button), "clicked",
+// GTK_SIGNAL_FUNC(Html_reset_form), html_lb);
+// } else
+// return;
+//
+// value = Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
+// name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
+//
+// Html_add_input(form, inp_type, (GtkWidget*)button, name, value,
+// NULL, FALSE);
+//
+// dFree(type);
+// dFree(name);
+// dFree(value);
+}
+
+
+/*
+ * <FONT>
+ */
+static void Html_tag_open_font(DilloHtml *html, char *tag, int tagsize)
+{
+#if 1
+ StyleAttrs style_attrs;
+ Style *old_style;
+ /*Font font;*/
+ const char *attrbuf;
+ int32_t color;
+
+ if (!prefs.force_my_colors) {
+ old_style = S_TOP(html)->style;
+ style_attrs = *old_style;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "color"))) {
+ if (prefs.contrast_visited_color && html->InVisitedLink) {
+ color = html->linkblock->visited_color;
+ } else {
+ /* use the tag-specified color */
+ color = Html_color_parse(
+ html, attrbuf, style_attrs.color->getColor());
+ style_attrs.color = Color::createSimple (HT2LT(html), color);
+ }
+ }
+
+#if 0
+ //if ((attrbuf = Html_get_attr(html, tag, tagsize, "face"))) {
+ // font = *( style_attrs.font );
+ // font.name = attrbuf;
+ // style_attrs.font = a_Dw_style_font_new_from_list (&font);
+ //}
+#endif
+
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ old_style->unref ();
+ }
+
+#endif
+}
+
+/*
+ * <ABBR>
+ */
+static void Html_tag_open_abbr(DilloHtml *html, char *tag, int tagsize)
+{
+// DwTooltip *tooltip;
+// const char *attrbuf;
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "title"))) {
+// tooltip = a_Dw_tooltip_new_no_ref(attrbuf);
+// HTML_SET_TOP_ATTR(html, x_tooltip, tooltip);
+// }
+}
+
+/*
+ * <B>
+ */
+static void Html_tag_open_b(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 1, 1);
+}
+
+/*
+ * <STRONG>
+ */
+static void Html_tag_open_strong(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 1, 1);
+}
+
+/*
+ * <I>
+ */
+static void Html_tag_open_i(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <EM>
+ */
+static void Html_tag_open_em(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <CITE>
+ */
+static void Html_tag_open_cite(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <CENTER>
+ */
+static void Html_tag_open_center(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+ HTML_SET_TOP_ATTR(html, textAlign, TEXT_ALIGN_CENTER);
+}
+
+/*
+ * <ADDRESS>
+ */
+static void Html_tag_open_address(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <TT>
+ */
+static void Html_tag_open_tt(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+}
+
+/*
+ * Read image-associated tag attributes,
+ * create new image and add it to the html page (if add is TRUE).
+ */
+static DilloImage *Html_add_new_image(DilloHtml *html, char *tag,
+ int tagsize, StyleAttrs *style_attrs,
+ bool_t add)
+{
+ const int MAX_W = 6000, MAX_H = 6000;
+
+ DilloImage *Image;
+ char *width_ptr, *height_ptr, *alt_ptr;
+ const char *attrbuf;
+ Length l_w, l_h;
+ int space, w = 0, h = 0;
+
+// if (prefs.show_tooltip &&
+// (attrbuf = Html_get_attr(html, tag, tagsize, "title")))
+// style_attrs->x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf);
+
+ alt_ptr = Html_get_attr_wdef(html, tag, tagsize, "alt", NULL);
+ width_ptr = Html_get_attr_wdef(html, tag, tagsize, "width", NULL);
+ height_ptr = Html_get_attr_wdef(html, tag, tagsize, "height", NULL);
+ // Check for malicious values
+ // TODO: the same for percentage and relative lengths.
+ if (width_ptr) {
+ l_w = Html_parse_length (html, width_ptr);
+ w = isAbsLength(l_w) ? absLengthVal(l_w) : 0;
+ }
+ if (height_ptr) {
+ l_h = Html_parse_length (html, height_ptr);
+ h = isAbsLength(l_h) ? absLengthVal(l_h) : 0;
+ }
+ if (w < 0 || h < 0 || abs(w*h) > MAX_W * MAX_H) {
+ dFree(width_ptr);
+ dFree(height_ptr);
+ width_ptr = height_ptr = NULL;
+ MSG("Html_add_new_image: suspicious image size request %dx%d\n", w, h);
+ }
+
+ /* todo: we should scale the image respecting its ratio.
+ * As the image size is not known at this time, maybe a flag
+ * can be set to scale it later.
+ if ((width_ptr && !height_ptr) || (height_ptr && !width_ptr))
+ [...]
+ */
+
+ /* Spacing to the left and right */
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "hspace"))) {
+ space = strtol(attrbuf, NULL, 10);
+
+ if (space > 0)
+ style_attrs->margin.left = style_attrs->margin.right = space;
+ }
+
+ /* Spacing at the top and bottom */
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "vspace"))) {
+ space = strtol(attrbuf, NULL, 10);
+
+ if (space > 0)
+ style_attrs->margin.top = style_attrs->margin.bottom = space;
+ }
+
+ /* Add a new image widget to this page */
+ Image = a_Image_new(0, 0, alt_ptr, S_TOP(html)->current_bg_color);
+ if (add) {
+ Html_add_widget(html, (Widget*)Image->dw,
+ width_ptr, height_ptr, style_attrs);
+ }
+
+ dFree(width_ptr);
+ dFree(height_ptr);
+ dFree(alt_ptr);
+ return Image;
+}
+
+/*
+ * Tell cache to retrieve image
+ */
+static void Html_load_image(DilloHtml *html, DilloUrl *url, DilloImage *Image)
+{
+ DilloWeb *Web;
+ int ClientKey;
+ /* Fill a Web structure for the cache query */
+ Web = a_Web_new(url);
+ Web->bw = html->bw;
+ Web->Image = Image;
+ Web->flags |= WEB_Image;
+ /* Request image data from the cache */
+ if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) {
+ a_Bw_add_client(html->bw, ClientKey, 0);
+ a_Bw_add_url(html->bw, url);
+ }
+}
+
+/*
+ * Create a new Image struct and request the image-url to the cache
+ * (If it either hits or misses, is not relevant here; that's up to the
+ * cache functions)
+ */
+static void Html_tag_open_img(DilloHtml *html, char *tag, int tagsize)
+{
+ DilloImage *Image;
+ DilloUrl *url, *usemap_url;
+ Textblock *textblock;
+ StyleAttrs style_attrs;
+ const char *attrbuf;
+ int border;
+
+ /* This avoids loading images. Useful for viewing suspicious HTML email. */
+ if (URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe)
+ return;
+
+ if (!(attrbuf = Html_get_attr(html, tag, tagsize, "src")) ||
+ !(url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0)))
+ return;
+
+ textblock = DW2TB(html->dw);
+
+
+ usemap_url = NULL;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "usemap")))
+ /* todo: usemap URLs outside of the document are not used. */
+ usemap_url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+
+ style_attrs = *S_TOP(html)->style;
+
+ if (S_TOP(html)->style->x_link != -1 ||
+ usemap_url != NULL) {
+ /* Images within links */
+ border = 1;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "border")))
+ border = strtol (attrbuf, NULL, 10);
+
+ if (S_TOP(html)->style->x_link != -1) {
+ /* In this case we can use the text color */
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html),
+ style_attrs.color->getColor()));
+ } else {
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html),
+ html->linkblock->link_color));
+ }
+ style_attrs.setBorderStyle (BORDER_SOLID);
+ style_attrs.borderWidth.setVal (border);
+ }
+
+ Image = Html_add_new_image(html, tag, tagsize, &style_attrs, TRUE);
+ // Html_connect_signals(html, GTK_OBJECT(Image->dw));
+// gtk_signal_connect_after(GTK_OBJECT(Image->dw), "button_press_event",
+// GTK_SIGNAL_FUNC(Html_image_menu), html->bw);
+
+#if 0
+ /* Image maps */
+ if (Html_get_attr(html, tag, tagsize, "ismap")) {
+ /* BUG: if several ISMAP images follow each other without
+ * being separated with a word, only the first one is ISMAPed
+ */
+// a_Dw_image_set_ismap (Image->dw);
+ _MSG(" Html_tag_open_img: server-side map (ISMAP)\n");
+ } else if (S_TOP(html)->style->x_link != -1 &&
+ usemap_url == NULL)
+ /* For simple links, we have to suppress the "image_pressed" signal.
+ * This is overridden for USEMAP images. */
+// a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE);
+
+ if (usemap_url) {
+// a_Dw_image_set_usemap (Image->dw, &html->linkblock->maps, usemap_url);
+ a_Url_free (usemap_url);
+ }
+#endif
+
+ Html_load_image(html, url, Image);
+ a_Url_free(url);
+}
+
+/*
+ * <map>
+ */
+static void Html_tag_open_map(DilloHtml *html, char *tag, int tagsize)
+{
+ char *hash_name;
+ const char *attrbuf;
+ DilloUrl *url;
+
+ if (html->InFlags & IN_MAP) {
+ MSG_HTML("nested <map>\n");
+ } else {
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) {
+ hash_name = dStrconcat("#", attrbuf, NULL);
+ url = Html_url_new(html, hash_name, NULL, 0, 0, 0, 0);
+ //a_Dw_image_map_list_add_map (&html->linkblock->maps, url);
+ a_Url_free (url);
+ dFree(hash_name);
+ }
+ html->InFlags |= IN_MAP;
+ }
+}
+
+/*
+ * Handle close <MAP>
+ */
+static void Html_tag_close_map(DilloHtml *html, int TagIdx)
+{
+ html->InFlags &= ~IN_MAP;
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <AREA>
+ */
+static void Html_tag_open_area(DilloHtml *html, char *tag, int tagsize)
+{
+// // AL
+// /* todo: point must be a dynamic array */
+// GdkPoint point[1024];
+// DilloUrl* url;
+// const char *attrbuf;
+// int type = DW_IMAGE_MAP_SHAPE_RECT;
+// int nbpoints, link = -1;
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "shape"))) {
+// if (dStrcasecmp(attrbuf, "rect") == 0)
+// type = DW_IMAGE_MAP_SHAPE_RECT;
+// else if (dStrcasecmp(attrbuf, "circle") == 0)
+// type = DW_IMAGE_MAP_SHAPE_CIRCLE;
+// else if (dStrncasecmp(attrbuf, "poly", 4) == 0)
+// type = DW_IMAGE_MAP_SHAPE_POLY;
+// else
+// type = DW_IMAGE_MAP_SHAPE_RECT;
+// }
+// /* todo: add support for coords in % */
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "coords"))) {
+// /* Is this a valid poly ?
+// * rect = x0,y0,x1,y1 => 2
+// * circle = x,y,r => 2
+// * poly = x0,y0,x1,y1,x2,y2 minimum => 3 */
+// nbpoints = Html_read_coords(html, attrbuf, point);
+// } else
+// return;
+//
+// if (Html_get_attr(html, tag, tagsize, "nohref")) {
+// link = -1;
+// _MSG("nohref");
+// }
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) {
+// url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+// dReturn_if_fail ( url != NULL );
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "alt")))
+// a_Url_set_alt(url, attrbuf);
+//
+// link = Html_set_new_link(html, &url);
+// }
+//
+// a_Dw_image_map_list_add_shape(&html->linkblock->maps, type, link,
+// point, nbpoints);
+}
+
+
+/*
+ * Test and extract the link from a javascript instruction.
+ */
+static const char* Html_get_javascript_link(DilloHtml *html)
+{
+ size_t i;
+ char ch, *p1, *p2;
+ Dstr *Buf = html->attr_data;
+
+ if (dStrncasecmp("javascript", Buf->str, 10) == 0) {
+ i = strcspn(Buf->str, "'\"");
+ ch = Buf->str[i];
+ if ((ch == '"' || ch == '\'') &&
+ (p2 = strchr(Buf->str + i + 1 , ch))) {
+ p1 = Buf->str + i;
+ MSG_HTML("link depends on javascript()\n");
+ dStr_truncate(Buf, p2 - Buf->str);
+ dStr_erase(Buf, 0, p1 - Buf->str + 1);
+ }
+ }
+ return Buf->str;
+}
+
+/*
+ * Register an anchor for this page.
+ */
+static void Html_add_anchor(DilloHtml *html, const char *name)
+{
+ _MSG("Registering ANCHOR: %s\n", name);
+ if (!DW2TB(html->dw)->addAnchor (name, S_TOP(html)->style))
+ MSG_HTML("Anchor names must be unique within the document\n");
+ /*
+ * According to Sec. 12.2.1 of the HTML 4.01 spec, "anchor names that
+ * differ only in case may not appear in the same document", but
+ * "comparisons between fragment identifiers and anchor names must be
+ * done by exact (case-sensitive) match." We ignore the case issue and
+ * always test for exact matches. Moreover, what does uppercase mean
+ * for Unicode characters outside the ASCII range?
+ */
+}
+
+/*
+ * <A>
+ */
+static void Html_tag_open_a(DilloHtml *html, char *tag, int tagsize)
+{
+ StyleAttrs style_attrs;
+ Style *old_style;
+ DilloUrl *url;
+ const char *attrbuf;
+ char *buf;
+ int buf_size;
+
+ /* todo: add support for MAP with A HREF */
+ Html_tag_open_area(html, tag, tagsize);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) {
+ /* if it's a javascript link, extract the reference. */
+ if (tolower(attrbuf[0]) == 'j')
+ attrbuf = Html_get_javascript_link(html);
+
+ url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+ dReturn_if_fail ( url != NULL );
+
+ old_style = S_TOP(html)->style;
+ style_attrs = *old_style;
+
+ if (a_Capi_get_buf(url, &buf, &buf_size)) {
+ html->InVisitedLink = TRUE;
+ style_attrs.color = Color::createSimple (
+ HT2LT(html),
+ html->linkblock->visited_color
+/*
+ a_Color_vc(html->linkblock->visited_color,
+ S_TOP(html)->style->color->getColor(),
+ html->linkblock->link_color,
+ S_TOP(html)->current_bg_color),
+*/
+ );
+ } else {
+ style_attrs.color = Color::createSimple(HT2LT(html),
+ html->linkblock->link_color);
+ }
+
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "title")))
+// style_attrs.x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf);
+
+ style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE;
+ style_attrs.x_link = Html_set_new_link(html, &url);
+ style_attrs.cursor = CURSOR_POINTER;
+
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ old_style->unref ();
+ }
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) {
+ if (prefs.show_extra_warnings)
+ Html_check_name_val(html, attrbuf, "name");
+ /* html->NameVal is freed in Html_process_tag */
+ html->NameVal = a_Url_decode_hex_str(attrbuf);
+ Html_add_anchor(html, html->NameVal);
+ }
+}
+
+/*
+ * <A> close function
+ */
+static void Html_tag_close_a(DilloHtml *html, int TagIdx)
+{
+ html->InVisitedLink = FALSE;
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Insert underlined text in the page.
+ */
+static void Html_tag_open_u(DilloHtml *html, char *tag, int tagsize)
+{
+ Style *style;
+ StyleAttrs style_attrs;
+
+ style = S_TOP(html)->style;
+ style_attrs = *style;
+ style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE;
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ style->unref ();
+}
+
+/*
+ * Insert strike-through text. Used by <S>, <STRIKE> and <DEL>.
+ */
+static void Html_tag_open_strike(DilloHtml *html, char *tag, int tagsize)
+{
+ Style *style;
+ StyleAttrs style_attrs;
+
+ style = S_TOP(html)->style;
+ style_attrs = *style;
+ style_attrs.textDecoration |= TEXT_DECORATION_LINE_THROUGH;
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ style->unref ();
+}
+
+/*
+ * <BLOCKQUOTE>
+ */
+static void Html_tag_open_blockquote(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 40, 9);
+}
+
+/*
+ * Handle the <UL> tag.
+ */
+static void Html_tag_open_ul(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ ListStyleType list_style_type;
+
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 0, 9);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "type"))) {
+ /* list_style_type explicitly defined */
+ if (dStrncasecmp(attrbuf, "disc", 4) == 0)
+ list_style_type = LIST_STYLE_TYPE_DISC;
+ else if (dStrncasecmp(attrbuf, "circle", 6) == 0)
+ list_style_type = LIST_STYLE_TYPE_CIRCLE;
+ else if (dStrncasecmp(attrbuf, "square", 6) == 0)
+ list_style_type = LIST_STYLE_TYPE_SQUARE;
+ else
+ /* invalid value */
+ list_style_type = LIST_STYLE_TYPE_DISC;
+ } else {
+ if (S_TOP(html)->list_type == HTML_LIST_UNORDERED) {
+ /* Nested <UL>'s. */
+ /* --EG :: I changed the behavior here : types are cycling instead of
+ * being forced to square. It's easier for mixed lists level counting.
+ */
+ switch (S_TOP(html)->style->listStyleType) {
+ case LIST_STYLE_TYPE_DISC:
+ list_style_type = LIST_STYLE_TYPE_CIRCLE;
+ break;
+ case LIST_STYLE_TYPE_CIRCLE:
+ list_style_type = LIST_STYLE_TYPE_SQUARE;
+ break;
+ case LIST_STYLE_TYPE_SQUARE:
+ default: /* this is actually a bug */
+ list_style_type = LIST_STYLE_TYPE_DISC;
+ break;
+ }
+ } else {
+ /* Either first <UL>, or a <OL> before. */
+ list_style_type = LIST_STYLE_TYPE_DISC;
+ }
+ }
+
+ HTML_SET_TOP_ATTR(html, listStyleType, list_style_type);
+ S_TOP(html)->list_type = HTML_LIST_UNORDERED;
+
+ S_TOP(html)->list_number = 0;
+ S_TOP(html)->ref_list_item = NULL;
+}
+
+/*
+ * Handle the <MENU> tag.
+ * (Deprecated and almost the same as <UL>)
+ */
+static void Html_tag_open_menu(DilloHtml *html, char *tag, int tagsize)
+{
+ ListStyleType list_style_type = LIST_STYLE_TYPE_DISC;
+
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 0, 9);
+ HTML_SET_TOP_ATTR(html, listStyleType, list_style_type);
+ S_TOP(html)->list_type = HTML_LIST_UNORDERED;
+ S_TOP(html)->list_number = 0;
+ S_TOP(html)->ref_list_item = NULL;
+
+ if (prefs.show_extra_warnings)
+ MSG_HTML("it is strongly recommended using <UL> instead of <MENU>\n");
+}
+
+/*
+ * Handle the <OL> tag.
+ */
+static void Html_tag_open_ol(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ ListStyleType list_style_type;
+ int n = 1;
+
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 0, 9);
+
+ list_style_type = LIST_STYLE_TYPE_DECIMAL;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "type"))) {
+ if (*attrbuf == '1')
+ list_style_type = LIST_STYLE_TYPE_DECIMAL;
+ else if (*attrbuf == 'a')
+ list_style_type = LIST_STYLE_TYPE_LOWER_ALPHA;
+ else if (*attrbuf == 'A')
+ list_style_type = LIST_STYLE_TYPE_UPPER_ALPHA;
+ else if (*attrbuf == 'i')
+ list_style_type = LIST_STYLE_TYPE_LOWER_ROMAN;
+ else if (*attrbuf == 'I')
+ list_style_type = LIST_STYLE_TYPE_UPPER_ROMAN;
+ }
+
+ HTML_SET_TOP_ATTR(html, listStyleType, list_style_type);
+ S_TOP(html)->list_type = HTML_LIST_ORDERED;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "start")) &&
+ (n = (int) strtol(attrbuf, NULL, 10)) < 0) {
+ MSG_HTML( "illegal '-' character in START attribute; Starting from 0\n");
+ n = 0;
+ }
+ S_TOP(html)->list_number = n;
+ S_TOP(html)->ref_list_item = NULL;
+}
+
+/*
+ * Handle the <LI> tag.
+ */
+static void Html_tag_open_li(DilloHtml *html, char *tag, int tagsize)
+{
+ StyleAttrs style_attrs;
+ Style *item_style, *word_style;
+ Widget **ref_list_item;
+ ListItem *list_item;
+ int *list_number;
+ const char *attrbuf;
+ char buf[16];
+
+ /* Get our parent tag's variables (used as state storage) */
+ list_number = &html->stack->getRef(html->stack->size()-2)->list_number;
+ ref_list_item = &html->stack->getRef(html->stack->size()-2)->ref_list_item;
+
+ /* set the item style */
+ word_style = S_TOP(html)->style;
+ style_attrs = *word_style;
+ //style_attrs.backgroundColor = Color::createSimple (HT2LT(html), 0xffff40);
+ //style_attrs.setBorderColor (Color::createSimple (HT2LT(html), 0x000000));
+ //style_attrs.setBorderStyle (BORDER_SOLID);
+ //style_attrs.borderWidth.setVal (1);
+ item_style = Style::create (HT2LT(html), &style_attrs);
+
+ DW2TB(html->dw)->addParbreak (2, word_style);
+
+ list_item = new ListItem ((ListItem *)*ref_list_item, false);
+ DW2TB(html->dw)->addWidget (list_item, item_style);
+ DW2TB(html->dw)->addParbreak (2, word_style);
+ *ref_list_item = list_item;
+ S_TOP(html)->textblock = html->dw = list_item;
+ item_style->unref();
+
+ switch (S_TOP(html)->list_type) {
+ case HTML_LIST_ORDERED:
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "value")) &&
+ (*list_number = strtol(attrbuf, NULL, 10)) < 0) {
+ MSG_HTML("illegal negative LIST VALUE attribute; Starting from 0\n");
+ *list_number = 0;
+ }
+ numtostr((*list_number)++, buf, 16, S_TOP(html)->style->listStyleType);
+ list_item->initWithText (dStrdup(buf), word_style);
+ list_item->addSpace (word_style);
+ break;
+ case HTML_LIST_NONE:
+ MSG_HTML("<li> outside <ul> or <ol>\n");
+ default:
+ list_item->initWithWidget (new Bullet(), word_style);
+ list_item->addSpace (word_style);
+ break;
+ }
+
+ // list_item->flush (); Looks like there's no need to flush.
+ // If it MUST be done, it could be inside Html_tag_close_li().
+}
+
+/*
+ * Close <LI>
+ */
+static void Html_tag_close_li(DilloHtml *html, int TagIdx)
+{
+ ((ListItem *)html->dw)->flush ();
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <HR>
+ */
+static void Html_tag_open_hr(DilloHtml *html, char *tag, int tagsize)
+{
+ Widget *hruler;
+ StyleAttrs style_attrs;
+ Style *style;
+ char *width_ptr;
+ const char *attrbuf;
+ int32_t size = 0;
+
+ width_ptr = Html_get_attr_wdef(html, tag, tagsize, "width", "100%");
+
+ style_attrs = *S_TOP(html)->style;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "size")))
+ size = strtol(attrbuf, NULL, 10);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "align"))) {
+ if (dStrcasecmp (attrbuf, "left") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_LEFT;
+ else if (dStrcasecmp (attrbuf, "right") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_RIGHT;
+ else if (dStrcasecmp (attrbuf, "center") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_CENTER;
+ }
+
+ /* todo: evaluate attribute */
+ if (Html_get_attr(html, tag, tagsize, "noshade")) {
+ style_attrs.setBorderStyle (BORDER_SOLID);
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html), style_attrs.color->getColor()));
+ if (size < 1)
+ size = 1;
+ } else {
+ style_attrs.setBorderStyle (BORDER_INSET);
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html),
+ (S_TOP(html)->current_bg_color)));
+ if (size < 2)
+ size = 2;
+ }
+
+ style_attrs.borderWidth.top =
+ style_attrs.borderWidth.left = (size + 1) / 2;
+ style_attrs.borderWidth.bottom =
+ style_attrs.borderWidth.right = size / 2;
+ style = Style::create (HT2LT(html), &style_attrs);
+
+ DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style);
+ hruler = new Ruler();
+ hruler->setStyle (style);
+ DW2TB(html->dw)->addWidget (hruler, style);
+ style->unref ();
+
+ //DW2TB(html->dw)->addWidget (hruler, S_TOP(html)->style);
+ DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style);
+ dFree(width_ptr);
+}
+
+/*
+ * <DL>
+ */
+static void Html_tag_open_dl(DilloHtml *html, char *tag, int tagsize)
+{
+ /* may want to actually do some stuff here. */
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+}
+
+/*
+ * <DT>
+ */
+static void Html_tag_open_dt(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_set_top_font(html, NULL, 0, 1, 1);
+}
+
+/*
+ * <DD>
+ */
+static void Html_tag_open_dd(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 40, 9);
+}
+
+/*
+ * <PRE>
+ */
+static void Html_tag_open_pre(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+
+ /* Is the placement of this statement right? */
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_PRE;
+ HTML_SET_TOP_ATTR (html, whiteSpace, WHITE_SPACE_PRE);
+ html->pre_column = 0;
+ html->PreFirstChar = TRUE;
+ html->InFlags |= IN_PRE;
+}
+
+/*
+ * Custom close for <PRE>
+ */
+static void Html_tag_close_pre(DilloHtml *html, int TagIdx)
+{
+ html->InFlags &= ~IN_PRE;
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Check whether a tag is in the "excluding" element set for PRE
+ * Excl. Set = {IMG, OBJECT, APPLET, BIG, SMALL, SUB, SUP, FONT, BASEFONT}
+ */
+static int Html_tag_pre_excludes(int tag_idx)
+{
+ char *es_set[] = {"img", "object", "applet", "big", "small", "sub", "sup",
+ "font", "basefont", NULL};
+ static int ei_set[10], i;
+
+ /* initialize array */
+ if (!ei_set[0])
+ for (i = 0; es_set[i]; ++i)
+ ei_set[i] = Html_tag_index(es_set[i]);
+
+ for (i = 0; ei_set[i]; ++i)
+ if (tag_idx == ei_set[i])
+ return 1;
+ return 0;
+}
+
+/*
+ * Handle <FORM> tag
+ */
+static void Html_tag_open_form(DilloHtml *html, char *tag, int tagsize)
+{
+ DilloUrl *action;
+ DilloHtmlMethod method;
+ DilloHtmlEnc enc;
+ const char *attrbuf;
+
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+
+ if (html->InFlags & IN_FORM) {
+ MSG_HTML("nested forms\n");
+ return;
+ }
+ html->InFlags |= IN_FORM;
+
+ method = DILLO_HTML_METHOD_GET;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "method"))) {
+ if (!dStrcasecmp(attrbuf, "post"))
+ method = DILLO_HTML_METHOD_POST;
+ /* todo: maybe deal with unknown methods? */
+ }
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "action")))
+ action = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+ else
+ action = a_Url_dup(html->linkblock->base_url);
+ enc = DILLO_HTML_ENC_URLENCODING;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "encoding"))) {
+ /* todo: maybe deal with unknown encodings? */
+ }
+ Html_form_new(html->linkblock, method, action, enc);
+ a_Url_free(action);
+}
+
+static void Html_tag_close_form(DilloHtml *html, int TagIdx)
+{
+ static char *SubmitTag =
+ "<input type='submit' value='?Submit?' alt='dillo-generated-button'>";
+ DilloHtmlForm *form;
+// int i;
+
+ if (html->InFlags & IN_FORM) {
+ form = html->linkblock->forms->getRef (
+ html->linkblock->forms->size() - 1);
+ /* If we don't have a submit button and the user desires one,
+ let's add a custom one */
+ if (form->num_submit_buttons == 0) {
+ if (prefs.show_extra_warnings || form->num_entry_fields != 1)
+ MSG_HTML("FORM lacks a Submit button\n");
+ if (prefs.generate_submit) {
+ MSG_HTML(" (added a submit button internally)\n");
+ Html_tag_open_input(html, SubmitTag, strlen(SubmitTag));
+ form->num_submit_buttons = 0;
+ }
+ }
+
+// /* Make buttons sensitive again */
+// for (i = 0; i < form->inputs->size(); i++) {
+// input_i = form->inputs->getRef(i);
+// /* Check for tricky HTML (e.g. <input type=image>) */
+// if (!input_i->widget)
+// continue;
+// if (input_i->type == DILLO_HTML_INPUT_SUBMIT ||
+// input_i->type == DILLO_HTML_INPUT_RESET) {
+// gtk_widget_set_sensitive(input_i->widget, TRUE);
+// } else if (input_i->type == DILLO_HTML_INPUT_IMAGE ||
+// input_i->type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||
+// input_i->type == DILLO_HTML_INPUT_BUTTON_RESET) {
+// a_Dw_button_set_sensitive(DW_BUTTON(input_i->widget), TRUE);
+// }
+// }
+ }
+ html->InFlags &= ~IN_FORM;
+ html->InFlags &= ~IN_SELECT;
+ html->InFlags &= ~IN_TEXTAREA;
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle <META>
+ * We do not support http-equiv=refresh because it's non standard,
+ * (the HTML 4.01 SPEC recommends explicitily to avoid it), and it
+ * can be easily abused!
+ *
+ * More info at:
+ * http://lists.w3.org/Archives/Public/www-html/2000Feb/thread.html#232
+ *
+ * todo: Note that we're sending custom HTML while still IN_HEAD. This
+ * is a hackish way to put the message. A much cleaner approach is to
+ * build a custom widget for it.
+ */
+static void Html_tag_open_meta(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *meta_template =
+"<table width='100%%'><tr><td bgcolor='#ee0000'>Warning:</td>\n"
+" <td bgcolor='#8899aa' width='100%%'>\n"
+" This page uses the NON-STANDARD meta refresh tag.<br> The HTML 4.01 SPEC\n"
+" (sec 7.4.4) recommends explicitly to avoid it.</td></tr>\n"
+" <tr><td bgcolor='#a0a0a0' colspan='2'>The author wanted you to go\n"
+" <a href='%s'>here</a>%s</td></tr></table><br>\n";
+
+ const char *equiv, *content;
+ char delay_str[64];
+ Dstr *ds_msg;
+ int delay;
+
+ /* only valid inside HEAD */
+ if (!(html->InFlags & IN_HEAD)) {
+ MSG_HTML("META elements must be inside the HEAD section\n");
+ return;
+ }
+
+ if ((equiv = Html_get_attr(html, tag, tagsize, "http-equiv")) &&
+ !dStrcasecmp(equiv, "refresh") &&
+ (content = Html_get_attr(html, tag, tagsize, "content"))) {
+
+ /* Get delay, if present, and make a message with it */
+ if ((delay = strtol(content, NULL, 0)))
+ snprintf(delay_str, 64, " after %d second%s.",
+ delay, (delay > 1) ? "s" : "");
+ else
+ sprintf(delay_str, ".");
+
+ /* Skip to anything after "URL=" */
+ while (*content && *(content++) != '=');
+
+ /* Send a custom HTML message
+ * todo: this is a hairy hack, It'd be much better to build a widget. */
+ ds_msg = dStr_sized_new(256);
+ dStr_sprintf(ds_msg, meta_template, content, delay_str);
+ {
+ int SaveFlags = html->InFlags;
+ html->InFlags = IN_BODY;
+ html->TagSoup = FALSE;
+ Html_write_raw(html, ds_msg->str, ds_msg->len, 0);
+ html->TagSoup = TRUE;
+ html->InFlags = SaveFlags;
+ }
+ dStr_free(ds_msg, 1);
+ }
+}
+
+/*
+ * Set the history of the menu to be consistent with the active menuitem.
+ */
+//static void Html_select_set_history(DilloHtmlInput *input)
+//{
+// int i;
+//
+// for (i = 0; i < input->select->num_options; i++) {
+// if (GTK_CHECK_MENU_ITEM(input->select->options[i].menuitem)->active) {
+// gtk_option_menu_set_history(GTK_OPTION_MENU(input->widget), i);
+// break;
+// }
+// }
+//}
+
+/*
+ * Reset the input widget to the initial value.
+ */
+static void Html_reset_input(DilloHtmlInput *input)
+{
+ int i;
+
+ switch (input->type) {
+ case DILLO_HTML_INPUT_TEXT:
+ case DILLO_HTML_INPUT_PASSWORD:
+ EntryResource *entryres;
+ entryres = (EntryResource*)((Embed*)input->widget)->getResource();
+ entryres->setText(input->init_str ? input->init_str : "");
+ break;
+ case DILLO_HTML_INPUT_CHECKBOX:
+ case DILLO_HTML_INPUT_RADIO:
+ ToggleButtonResource *tb_r;
+ tb_r = (ToggleButtonResource*)((Embed*)input->widget)->getResource();
+ tb_r->setActivated(input->init_val);
+ break;
+ case DILLO_HTML_INPUT_SELECT:
+ if (input->select != NULL) {
+ /* this is in reverse order so that, in case more than one was
+ * selected, we get the last one, which is consistent with handling
+ * of multiple selected options in the layout code. */
+ for (i = input->select->num_options - 1; i >= 0; i--) {
+ if (input->select->options[i].init_val) {
+// gtk_menu_item_activate(GTK_MENU_ITEM
+// (input->select->options[i].menuitem));
+// Html_select_set_history(input);
+ break;
+ }
+ }
+ }
+ break;
+ case DILLO_HTML_INPUT_SEL_LIST:
+ if (!input->select)
+ break;
+ for (i = 0; i < input->select->num_options; i++) {
+// if (input->select->options[i].init_val) {
+// if (input->select->options[i].menuitem->state == GTK_STATE_NORMAL)
+// gtk_list_select_child(GTK_LIST(input->select->menu),
+// input->select->options[i].menuitem);
+// } else {
+// if (input->select->options[i].menuitem->state==GTK_STATE_SELECTED)
+// gtk_list_unselect_child(GTK_LIST(input->select->menu),
+// input->select->options[i].menuitem);
+// }
+ }
+ break;
+ case DILLO_HTML_INPUT_TEXTAREA:
+ if (input->init_str != NULL) {
+// int pos = 0;
+// gtk_editable_delete_text(GTK_EDITABLE(input->widget), 0, -1);
+// gtk_editable_insert_text(GTK_EDITABLE(input->widget), input->init_str,
+// strlen(input->init_str), &pos);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+
+/*
+ * Add a new input to the form data structure, setting the initial
+ * values.
+ */
+static void Html_add_input(DilloHtmlForm *form,
+ DilloHtmlInputType type,
+ Widget *widget,
+ Embed *embed,
+ const char *name,
+ const char *init_str,
+ DilloHtmlSelect *select,
+ bool_t init_val)
+{
+ DilloHtmlInput *input;
+
+ _MSG("name=[%s] init_str=[%s] init_val=[%d]\n",
+ name, init_str, init_val);
+ form->inputs->increase();
+ input = form->inputs->getRef (form->inputs->size() - 1);
+ input->type = type;
+ input->widget = widget;
+ input->embed = embed;
+ input->name = (name) ? dStrdup(name) : NULL;
+ input->init_str = (init_str) ? dStrdup(init_str) : NULL;
+ input->select = select;
+ input->init_val = init_val;
+ Html_reset_input(input);
+
+ /* some stats */
+ if (type == DILLO_HTML_INPUT_PASSWORD ||
+ type == DILLO_HTML_INPUT_TEXT ||
+ type == DILLO_HTML_INPUT_TEXTAREA) {
+ form->num_entry_fields++;
+ } else if (type == DILLO_HTML_INPUT_SUBMIT ||
+ type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||
+ type == DILLO_HTML_INPUT_IMAGE) {
+ form->num_submit_buttons++;
+ }
+}
+
+
+/*
+ * Given a GtkWidget, find the form that contains it.
+ * Return value: form_index if successful, -1 otherwise.
+ */
+//static int Html_find_form(GtkWidget *reset, DilloHtmlLB *html_lb)
+//{
+// int form_index;
+// int input_idx;
+// DilloHtmlForm *form;
+//
+// for (form_index = 0; form_index < html_lb->forms->size(); form_index++) {
+// form = html_lb->forms->getRef (form_index);
+// for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) {
+// if (form->inputs->get(input_idx).widget == reset) {
+// return form_index;
+// }
+// }
+// }
+// return -1;
+//}
+
+/*
+ * Reset all inputs in the form containing reset to their initial values.
+ * In general, reset is the reset button for the form.
+ */
+//static void Html_reset_form(GtkWidget *reset, DilloHtmlLB *html_lb)
+//{
+// int i, j;
+// DilloHtmlForm *form;
+//
+// if ((i = Html_find_form(reset, html_lb)) != -1){
+// form = html_lb->forms->getRef (i);
+// for ( j = 0; j < form->inputs->size(); j++)
+// Html_reset_input(&(form->inputs[j]));
+// }
+//}
+
+/*
+ * Urlencode 'val' and append it to 'str'
+ */
+static void Html_urlencode_append(Dstr *str, const char *val)
+{
+ char *enc_val = a_Url_encode_hex_str(val);
+ dStr_append(str, enc_val);
+ dFree(enc_val);
+}
+
+/*
+ * Append a name-value pair to an existing url.
+ * (name and value are urlencoded before appending them)
+ */
+static void
+ Html_append_input(Dstr *url, const char *name, const char *value)
+{
+ if (name != NULL) {
+ Html_urlencode_append(url, name);
+ dStr_append_c(url, '=');
+ Html_urlencode_append(url, value);
+ dStr_append_c(url, '&');
+ }
+}
+
+/*
+ * Append a image button click position to an existing url.
+ */
+//static void Html_append_clickpos(Dstr *url, const char *name, int x, int y)
+//{
+// if (name) {
+// Html_urlencode_append(url, name);
+// dStr_sprintfa(url, ".x=%d&", x);
+// Html_urlencode_append(url, name);
+// dStr_sprintfa(url, ".y=%d&", y);
+// } else
+// dStr_sprintfa(url, "x=%d&y=%d&", x, y);
+//}
+
+/*
+ * Submit the form containing the submit input by making a new query URL
+ * and sending it with a_Nav_push.
+ * (Called by GTK+)
+ * click_x and click_y are used only by input images and are set only when
+ * called by Html_image_clicked. GTK+ does NOT give these arguments.
+ */
+static void Html_submit_form2(DilloHtmlLB *html_lb, DilloHtmlForm *form,
+ int e_input_idx)
+{
+ int input_idx;
+ DilloHtmlInput *input;
+ DilloUrl *new_url;
+ char *url_str, *action_str, *p;
+
+ if ((form->method == DILLO_HTML_METHOD_GET) ||
+ (form->method == DILLO_HTML_METHOD_POST)) {
+ Dstr *DataStr = dStr_sized_new(4096);
+
+ _MSG("Html_submit_form2: form->action=%s\n",URL_STR_(form->action));
+
+ for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) {
+ input = form->inputs->getRef (input_idx);
+ switch (input->type) {
+ case DILLO_HTML_INPUT_TEXT:
+ case DILLO_HTML_INPUT_PASSWORD:
+ EntryResource *entryres;
+ entryres = (EntryResource*)((Embed*)input->widget)->getResource();
+ Html_append_input(DataStr, input->name, entryres->getText());
+ break;
+ case DILLO_HTML_INPUT_CHECKBOX:
+ case DILLO_HTML_INPUT_RADIO:
+ ToggleButtonResource *cb_r;
+ cb_r=(ToggleButtonResource*)((Embed*)input->widget)->getResource();
+ if (input->name && input->init_str && cb_r->isActivated()) {
+ Html_append_input(DataStr, input->name, input->init_str);
+ }
+ break;
+ case DILLO_HTML_INPUT_HIDDEN:
+ Html_append_input(DataStr, input->name, input->init_str);
+ break;
+// case DILLO_HTML_INPUT_SELECT:
+// for (i = 0; i < input->select->num_options; i++) {
+// if (GTK_CHECK_MENU_ITEM(input->select->options[i].menuitem)->
+// active) {
+// Html_append_input(DataStr, input->name,
+// input->select->options[i].value);
+// break;
+// }
+// }
+// break;
+// case DILLO_HTML_INPUT_SEL_LIST:
+// for (i = 0; i < input->select->num_options; i++) {
+// if (input->select->options[i].menuitem->state ==
+// GTK_STATE_SELECTED) {
+// Html_append_input(DataStr, input->name,
+// input->select->options[i].value);
+// }
+// }
+// break;
+// case DILLO_HTML_INPUT_TEXTAREA:
+// text = gtk_editable_get_chars(GTK_EDITABLE (input->widget),0,-1);
+// Html_append_input(DataStr, input->name, text);
+// dFree(text);
+// break;
+// case DILLO_HTML_INPUT_INDEX:
+// Html_urlencode_append(DataStr,
+// gtk_entry_get_text(GTK_ENTRY(input->widget)));
+// break;
+// case DILLO_HTML_INPUT_IMAGE:
+// if (input->widget == submit) {
+// Html_append_input(DataStr, input->name, input->init_str);
+// Html_append_clickpos(DataStr, input->name, click_x, click_y);
+// }
+// break;
+ case DILLO_HTML_INPUT_SUBMIT:
+ case DILLO_HTML_INPUT_BUTTON_SUBMIT:
+ if (input_idx == e_input_idx && form->num_submit_buttons > 0)
+ Html_append_input(DataStr, input->name, input->init_str);
+ break;
+ default:
+ break;
+ } /* switch */
+ } /* for (inputs) */
+
+ if (DataStr->str[DataStr->len - 1] == '&')
+ dStr_truncate(DataStr, DataStr->len - 1);
+
+ /* form->action was previously resolved against base URL */
+ action_str = dStrdup(URL_STR(form->action));
+
+ if (form->method == DILLO_HTML_METHOD_POST) {
+ new_url = a_Url_new(action_str, NULL, 0, 0, 0);
+ a_Url_set_data(new_url, DataStr->str);
+ a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Post);
+ } else {
+ /* remove <fragment> and <query> sections if present */
+ if ((p = strchr(action_str, '#')))
+ *p = 0;
+ if ((p = strchr(action_str, '?')))
+ *p = 0;
+
+ url_str = dStrconcat(action_str, "?", DataStr->str, NULL);
+ new_url = a_Url_new(url_str, NULL, 0, 0, 0);
+ a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Get);
+ dFree(url_str);
+ }
+
+ a_Nav_push(html_lb->bw, new_url);
+ dFree(action_str);
+ dStr_free(DataStr, TRUE);
+ a_Url_free(new_url);
+ } else {
+ MSG("Html_submit_form2: Method unknown\n");
+ }
+
+// /* now, make the rendered area have its focus back */
+// gtk_widget_grab_focus(GTK_BIN(html_lb->bw->render_main_scroll)->child);
+}
+
+/*
+ * Handler for events related to forms.
+ *
+ * TODO: Currently there's "clicked" for buttons, we surely need "enter" for
+ * textentries, and maybe the "mouseover, ...." set for Javascript.
+ */
+void a_Html_form_event_handler(void *data,
+ form::Form *form_receiver,
+ void *v_resource)
+{
+ int form_index, input_idx = -1, idx;
+ DilloHtmlForm *form = NULL;
+ DilloHtmlLB *html_lb = (DilloHtmlLB*)data;
+
+ printf("Html_form_event_handler %p %p\n", html_lb, form_receiver);
+
+ /* Search the form that generated the submit event */
+ for (form_index = 0; form_index < html_lb->forms->size(); form_index++) {
+ form = html_lb->forms->getRef (form_index);
+ if (form->form_receiver == form_receiver) {
+ /* form found, let's get the input index for this event */
+ for (idx = 0; idx < form->inputs->size(); idx++) {
+ DilloHtmlInput *input = form->inputs->getRef(idx);
+ if (input->embed &&
+ v_resource == (void*)((Embed*)input->widget)->getResource()) {
+ input_idx = idx;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (form_index == html_lb->forms->size()) {
+ MSG("a_Html_form_event_handler: ERROR, form not found!\n");
+ } else {
+ Html_submit_form2(html_lb, form, input_idx);
+ }
+}
+
+/*
+ * Submit form if it has no submit button.
+ * (Called by GTK+ when the user presses enter in a text entry within a form)
+ */
+//static void Html_enter_submit_form(GtkWidget *submit, DilloHtmlLB *html_lb)
+//{
+// int i;
+//
+// /* Search the form that generated the submit event */
+// if ((i = Html_find_form(submit, html_lb)) == -1)
+// return;
+//
+// /* Submit on enterpress when there's a single text-entry only,
+// * or if the user set enter to always submit */
+// if ((html_lb->forms->getRef(i))->.num_entry_fields == 1) ||
+// prefs.enterpress_forces_submit)
+// Html_submit_form(submit, html_lb, 1, 1);
+//}
+
+/*
+ * Call submit form, when input image has been clicked
+ */
+//static void Html_image_clicked(Widget *widget, int x, int y,
+// DilloHtmlLB *lb)
+//{
+// _MSG("Hallo! (%d, %d, %p)\n", x, y, lb);
+// Html_submit_form((GtkWidget*) widget, lb, x, y);
+//}
+
+/*
+ * Create input image for the form
+ */
+static Widget *Html_input_image(DilloHtml *html, char *tag, int tagsize,
+ DilloHtmlLB *html_lb, DilloUrl *action)
+{
+// // AL
+// DilloImage *Image;
+// Widget *button;
+// DilloUrl *url = NULL;
+// Style style_attrs;
+// const char *attrbuf;
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "src")) &&
+// (url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0))) {
+// button = a_Dw_button_new (0, FALSE);
+// DW2TB(html->dw)->addWidget (button,
+// S_TOP(html)->style);
+// gtk_signal_connect(GTK_OBJECT(button), "clicked_at",
+// GTK_SIGNAL_FUNC(Html_image_clicked), html_lb);
+// a_Dw_button_set_sensitive(DW_BUTTON(button), FALSE);
+//
+// /* create new image and add it to the button */
+// if ((Image = Html_add_new_image(html, tag, tagsize, &style_attrs,
+// FALSE))) {
+// /* By suppressing the "image_pressed" signal, the events are sent
+// * to the parent DwButton */
+// a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE);
+// IM2DW(Image->dw)->setStyle (S_TOP(html)->style);
+// a_Dw_container_add(DW_CONTAINER(button), IM2DW(Image->dw));
+// IM2DW(Image->dw)->setCursor (CURSOR_HAND);
+// Html_load_image(html, url, Image);
+// a_Url_free(url);
+// return button;
+// }
+// }
+//
+// DEBUG_MSG(10, "Html_input_image: unable to create image submit.\n");
+// a_Url_free(url);
+ return NULL;
+}
+
+/*
+ * Add a new input to current form
+ */
+static void Html_tag_open_input(DilloHtml *html, char *tag, int tagsize)
+{
+ DilloHtmlForm *form;
+ DilloHtmlInputType inp_type;
+ DilloHtmlLB *html_lb;
+ Widget *widget = NULL;
+ Embed *embed = NULL;
+ char *value, *name, *type, *init_str;
+// const char *attrbuf, *label;
+ bool_t init_val = FALSE;
+ int input_idx;
+
+ if (!(html->InFlags & IN_FORM)) {
+ MSG_HTML("input camp outside <form>\n");
+ return;
+ }
+
+ html_lb = html->linkblock;
+ form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+
+ /* Get 'value', 'name' and 'type' */
+ value = Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
+ name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
+ type = Html_get_attr_wdef(html, tag, tagsize, "type", "");
+
+ init_str = NULL;
+ inp_type = DILLO_HTML_INPUT_UNKNOWN;
+ if (!dStrcasecmp(type, "password")) {
+ inp_type = DILLO_HTML_INPUT_PASSWORD;
+ EntryResource *entryResource =
+ HT2LT(html)->getResourceFactory()->createEntryResource (15, true);
+ embed = new Embed (entryResource);
+ widget = embed;
+ init_str = (value) ? value : NULL;
+ } else if (!dStrcasecmp(type, "checkbox")) {
+ inp_type = DILLO_HTML_INPUT_CHECKBOX;
+ CheckButtonResource *check_b_r = HT2LT(html)->getResourceFactory()
+ ->createCheckButtonResource(false);
+ embed = new Embed (check_b_r);
+ widget = embed;
+ init_val = (Html_get_attr(html, tag, tagsize, "checked") != NULL);
+ init_str = (value) ? value : dStrdup("on");
+ } else if (!dStrcasecmp(type, "radio")) {
+ inp_type = DILLO_HTML_INPUT_RADIO;
+ RadioButtonResource *rb_r = NULL;
+ for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) {
+ DilloHtmlInput *input = form->inputs->getRef(input_idx);
+ if (input->type == DILLO_HTML_INPUT_RADIO &&
+ (input->name && !dStrcasecmp(input->name, name)) ) {
+ rb_r =(RadioButtonResource*)((Embed*)input->widget)->getResource();
+ break;
+ }
+ }
+ rb_r = HT2LT(html)->getResourceFactory()
+ ->createRadioButtonResource(rb_r, false);
+ embed = new Embed (rb_r);
+ widget = embed;
+
+ init_val = (Html_get_attr(html, tag, tagsize, "checked") != NULL);
+ init_str = (value) ? value : NULL;
+ } else if (!dStrcasecmp(type, "hidden")) {
+ inp_type = DILLO_HTML_INPUT_HIDDEN;
+ if (value)
+ init_str = dStrdup(Html_get_attr(html, tag, tagsize, "value"));
+ } else if (!dStrcasecmp(type, "submit")) {
+ inp_type = DILLO_HTML_INPUT_SUBMIT;
+ init_str = (value) ? value : dStrdup("submit");
+ LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory()
+ ->createLabelButtonResource(init_str);
+ widget = embed = new Embed (label_b_r);
+// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
+ label_b_r->connectClicked (form->form_receiver);
+ } else if (!dStrcasecmp(type, "reset")) {
+ inp_type = DILLO_HTML_INPUT_RESET;
+ init_str = (value) ? value : dStrdup("Reset");
+ LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory()
+ ->createLabelButtonResource(init_str);
+ widget = embed = new Embed (label_b_r);
+// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
+// gtk_signal_connect(GTK_OBJECT(widget), "clicked",
+// GTK_SIGNAL_FUNC(Html_reset_form), html_lb);
+// } else if (!dStrcasecmp(type, "image")) {
+// if (URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe) {
+// /* Don't request the image, make a text submit button instead */
+// inp_type = DILLO_HTML_INPUT_SUBMIT;
+// attrbuf = Html_get_attr(html, tag, tagsize, "alt");
+// label = attrbuf ? attrbuf : value ? value : name ? name : "Submit";
+// init_str = dStrdup(label);
+// widget = gtk_button_new_with_label(init_str);
+// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
+// gtk_signal_connect(GTK_OBJECT(widget), "clicked",
+// GTK_SIGNAL_FUNC(Html_submit_form), html_lb);
+// } else {
+// inp_type = DILLO_HTML_INPUT_IMAGE;
+// /* use a dw_image widget */
+// widget = (GtkWidget*) Html_input_image(html, tag, tagsize,
+// html_lb, form->action);
+// init_str = value;
+// }
+// } else if (!dStrcasecmp(type, "file")) {
+// /* todo: implement it! */
+// inp_type = DILLO_HTML_INPUT_FILE;
+// init_str = (value) ? value : NULL;
+// MSG("An input of the type \"file\" wasn't rendered!\n");
+ } else if (!dStrcasecmp(type, "button")) {
+ inp_type = DILLO_HTML_INPUT_BUTTON;
+ if (value) {
+ init_str = value;
+ LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory()
+ ->createLabelButtonResource(init_str);
+ widget = embed = new Embed (label_b_r);
+ }
+ } else if (!dStrcasecmp(type, "text") || !*type) {
+ /* Text input, which also is the default */
+ inp_type = DILLO_HTML_INPUT_TEXT;
+ EntryResource *entryResource =
+ //HT2LT(html)->getResourceFactory()->createEntryResource (15, false);
+ HT2LT(html)->getResourceFactory()->createEntryResource (10, false);
+ widget = embed = new Embed (entryResource);
+ init_str = (value) ? value : NULL;
+ } else {
+ /* Unknown input type */
+ MSG_HTML("Unknown input type: \"%s\"\n", type);
+ }
+
+ if (inp_type != DILLO_HTML_INPUT_UNKNOWN) {
+ Html_add_input(form, inp_type, widget, embed, name,
+ (init_str) ? init_str : "", NULL, init_val);
+ }
+
+ if (widget != NULL && inp_type != DILLO_HTML_INPUT_IMAGE &&
+ inp_type != DILLO_HTML_INPUT_UNKNOWN) {
+ if (inp_type == DILLO_HTML_INPUT_TEXT ||
+ inp_type == DILLO_HTML_INPUT_PASSWORD) {
+ EntryResource *entryres = (EntryResource*)embed->getResource();
+ /* Readonly or not? */
+ if (Html_get_attr(html, tag, tagsize, "readonly"))
+ entryres->setEditable(false);
+
+// /* Set width of the entry */
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "size")))
+// gtk_widget_set_usize(widget, (strtol(attrbuf, NULL, 10) + 1) *
+// gdk_char_width(widget->style->font, '0'), 0);
+//
+// /* Maximum length of the text in the entry */
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "maxlength")))
+// gtk_entry_set_max_length(GTK_ENTRY(widget),
+// strtol(attrbuf, NULL, 10));
+ }
+
+ DW2TB(html->dw)->addWidget (embed, S_TOP(html)->style);
+ }
+
+ dFree(type);
+ dFree(name);
+ if (init_str != value)
+ dFree(init_str);
+ dFree(value);
+}
+
+/*
+ * The ISINDEX tag is just a deprecated form of <INPUT type=text> with
+ * implied FORM, afaics.
+ */
+static void Html_tag_open_isindex(DilloHtml *html, char *tag, int tagsize)
+{
+// // AL
+// DilloHtmlForm *form;
+// DilloHtmlLB *html_lb;
+// DilloUrl *action;
+// GtkWidget *widget;
+// Widget *embed_gtk;
+// const char *attrbuf;
+//
+// html_lb = html->linkblock;
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "action")))
+// action = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+// else
+// action = a_Url_dup(html->linkblock->base_url);
+//
+// Html_form_new(html->linkblock, DILLO_HTML_METHOD_GET, action,
+// DILLO_HTML_ENC_URLENCODING);
+//
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+//
+// DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "prompt")))
+// DW2TB(html->dw)->addText(dStrdup(attrbuf),
+// S_TOP(html)->style);
+//
+// widget = gtk_entry_new();
+// Html_add_input(form, DILLO_HTML_INPUT_INDEX,
+// widget, NULL, NULL, NULL, FALSE);
+// gtk_signal_connect(GTK_OBJECT(widget), "activate",
+// GTK_SIGNAL_FUNC(Html_enter_submit_form),
+// html_lb);
+// gtk_widget_show(widget);
+// /* compare <input type=text> */
+// gtk_signal_connect_after(GTK_OBJECT(widget), "button_press_event",
+// GTK_SIGNAL_FUNC(gtk_true),
+// NULL);
+//
+// embed_gtk = a_Dw_embed_gtk_new();
+// a_Dw_embed_gtk_add_gtk(DW_EMBED_GTK(embed_gtk), widget);
+// DW2TB(html->dw)->addWidget (embed_gtk,
+// S_TOP(html)->style);
+//
+// a_Url_free(action);
+}
+
+/*
+ * Close textarea
+ * (TEXTAREA is parsed in VERBATIM mode, and entities are handled here)
+ */
+static void Html_tag_close_textarea(DilloHtml *html, int TagIdx)
+{
+ DilloHtmlLB *html_lb = html->linkblock;
+ char *str;
+ DilloHtmlForm *form;
+ int i;
+
+ if (html->InFlags & IN_FORM && html->InFlags & IN_TEXTAREA) {
+ /* Remove the line ending that follows the opening tag */
+ if (html->Stash->str[0] == '\r')
+ dStr_erase(html->Stash, 0, 1);
+ if (html->Stash->str[0] == '\n')
+ dStr_erase(html->Stash, 0, 1);
+
+ /* As the spec recommends to canonicalize line endings, it is safe
+ * to replace '\r' with '\n'. It will be canonicalized anyway! */
+ for (i = 0; i < html->Stash->len; ++i) {
+ if (html->Stash->str[i] == '\r') {
+ if (html->Stash->str[i + 1] == '\n')
+ dStr_erase(html->Stash, i, 1);
+ else
+ html->Stash->str[i] = '\n';
+ }
+ }
+
+ /* The HTML3.2 spec says it can have "text and character entities". */
+ str = Html_parse_entities(html, html->Stash->str, html->Stash->len);
+
+ form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+ form->inputs->get(form->inputs->size() - 1).init_str = str;
+// gtk_text_insert(GTK_TEXT(form->inputs[form->inputs->size() - 1].widget),
+// NULL, NULL, NULL, str, -1);
+
+ html->InFlags &= ~IN_TEXTAREA;
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * The textarea tag
+ * (todo: It doesn't support wrapping).
+ */
+static void Html_tag_open_textarea(DilloHtml *html, char *tag, int tagsize)
+{
+// // AL
+// DilloHtmlLB *html_lb;
+// DilloHtmlForm *form;
+// GtkWidget *widget;
+// GtkWidget *scroll;
+// Widget *embed_gtk;
+// char *name;
+// const char *attrbuf;
+// int cols, rows;
+//
+// /* We can't push a new <FORM> because the 'action' URL is unknown */
+// if (!(html->InFlags & IN_FORM)) {
+// MSG_HTML("<textarea> outside <form>\n");
+// html->ReqTagClose = TRUE;
+// return;
+// }
+// if (html->InFlags & IN_TEXTAREA) {
+// MSG_HTML("nested <textarea>\n");
+// html->ReqTagClose = TRUE;
+// return;
+// }
+//
+// html->InFlags |= IN_TEXTAREA;
+// html_lb = html->linkblock;
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+// Html_stash_init(html);
+// S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;
+//
+// cols = 20;
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "cols")))
+// cols = strtol(attrbuf, NULL, 10);
+// rows = 10;
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "rows")))
+// rows = strtol(attrbuf, NULL, 10);
+// name = NULL;
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "name")))
+// name = dStrdup(attrbuf);
+//
+// widget = gtk_text_new(NULL, NULL);
+// /* compare <input type=text> */
+// gtk_signal_connect_after(GTK_OBJECT(widget), "button_press_event",
+// GTK_SIGNAL_FUNC(gtk_true),
+// NULL);
+//
+// /* Calculate the width and height based on the cols and rows
+// * todo: Get it right... Get the metrics from the font that will be used.
+// */
+// gtk_widget_set_usize(widget, 6 * cols, 16 * rows);
+//
+// /* If the attribute readonly isn't specified we make the textarea
+// * editable. If readonly is set we don't have to do anything.
+// */
+// if (!Html_get_attr(html, tag, tagsize, "readonly"))
+// gtk_text_set_editable(GTK_TEXT(widget), TRUE);
+//
+// scroll = gtk_scrolled_window_new(NULL, NULL);
+// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+// GTK_POLICY_AUTOMATIC,
+// GTK_POLICY_AUTOMATIC);
+// gtk_container_add(GTK_CONTAINER(scroll), widget);
+// gtk_widget_show(widget);
+// gtk_widget_show(scroll);
+//
+// Html_add_input(form, DILLO_HTML_INPUT_TEXTAREA,
+// widget, name, NULL, NULL, FALSE);
+// dFree(name);
+//
+// embed_gtk = a_Dw_embed_gtk_new ();
+// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), scroll);
+// DW2TB(html->dw)->addWidget (embed_gtk,
+// S_TOP(html)->style);
+}
+
+/*
+ * <SELECT>
+ */
+/* The select tag is quite tricky, because of gorpy html syntax. */
+static void Html_tag_open_select(DilloHtml *html, char *tag, int tagsize)
+{
+// DilloHtmlForm *form;
+// DilloHtmlSelect *Select;
+// DilloHtmlLB *html_lb;
+// GtkWidget *widget, *menu;
+// char *name;
+// const char *attrbuf;
+// int size, type, multi;
+//
+// if (!(html->InFlags & IN_FORM)) {
+// MSG_HTML("<select> outside <form>\n");
+// return;
+// }
+// if (html->InFlags & IN_SELECT) {
+// MSG_HTML("nested <select>\n");
+// return;
+// }
+// html->InFlags |= IN_SELECT;
+//
+// html_lb = html->linkblock;
+//
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+//
+// name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
+//
+// size = 0;
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "size")))
+// size = strtol(attrbuf, NULL, 10);
+//
+// multi = (Html_get_attr(html, tag, tagsize, "multiple")) ? 1 : 0;
+// if (size < 1)
+// size = multi ? 10 : 1;
+//
+// if (size == 1) {
+// menu = gtk_menu_new();
+// widget = gtk_option_menu_new();
+// type = DILLO_HTML_INPUT_SELECT;
+// } else {
+// menu = gtk_list_new();
+// widget = menu;
+// if (multi)
+// gtk_list_set_selection_mode(GTK_LIST(menu), GTK_SELECTION_MULTIPLE);
+// type = DILLO_HTML_INPUT_SEL_LIST;
+// }
+//
+// Select = dNew(DilloHtmlSelect, 1);
+// Select->menu = menu;
+// Select->size = size;
+// Select->num_options = 0;
+// Select->num_options_max = 8;
+// Select->options = dNew(DilloHtmlOption, Select->num_options_max);
+//
+// Html_add_input(form, type, widget, name, NULL, Select, FALSE);
+// Html_stash_init(html);
+// dFree(name);
+}
+
+/*
+ * ?
+ */
+static void Html_option_finish(DilloHtml *html)
+{
+// DilloHtmlForm *form;
+// DilloHtmlInput *input;
+// GtkWidget *menuitem;
+// GSList *group;
+// DilloHtmlSelect *select;
+//
+// if (!(html->InFlags & IN_FORM))
+// return;
+//
+// form = html->linkblock->forms->getRef (html->linkblock->forms->size() - 1);
+// input = form->inputs->getRef (form->inputs->size() - 1);
+// if (input->select->num_options <= 0)
+// return;
+//
+// select = input->select;
+// if (input->type == DILLO_HTML_INPUT_SELECT ) {
+// if (select->num_options == 1)
+// group = NULL;
+// else
+// group = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM
+// (select->options[0].menuitem));
+// menuitem = gtk_radio_menu_item_new_with_label(group, html->Stash->str);
+// select->options[select->num_options - 1].menuitem = menuitem;
+// if (select->options[select->num_options - 1].value == NULL)
+// select->options[select->num_options - 1].value =
+// dStrdup(html->Stash->str);
+// gtk_menu_append(GTK_MENU(select->menu), menuitem);
+// if (select->options[select->num_options - 1].init_val)
+// gtk_menu_item_activate(GTK_MENU_ITEM(menuitem));
+// gtk_widget_show(menuitem);
+// } else if (input->type == DILLO_HTML_INPUT_SEL_LIST) {
+// menuitem = gtk_list_item_new_with_label(html->Stash->str);
+// select->options[select->num_options - 1].menuitem = menuitem;
+// if (select->options[select->num_options - 1].value == NULL)
+// select->options[select->num_options - 1].value =
+// dStrdup(html->Stash->str);
+// gtk_container_add(GTK_CONTAINER(select->menu), menuitem);
+// if (select->options[select->num_options - 1].init_val)
+// gtk_list_select_child(GTK_LIST(select->menu), menuitem);
+// gtk_widget_show(menuitem);
+// }
+}
+
+/*
+ * <OPTION>
+ */
+static void Html_tag_open_option(DilloHtml *html, char *tag, int tagsize)
+{
+// DilloHtmlForm *form;
+// DilloHtmlInput *input;
+// DilloHtmlLB *html_lb;
+// int no;
+//
+// if (!(html->InFlags & IN_SELECT))
+// return;
+//
+// html_lb = html->linkblock;
+//
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+// input = form->inputs->getRef (form->inputs->size() - 1);
+// if (input->type == DILLO_HTML_INPUT_SELECT ||
+// input->type == DILLO_HTML_INPUT_SEL_LIST) {
+// Html_option_finish(html);
+// no = input->select->num_options;
+// a_List_add(input->select->options, no, input->select->num_options_max);
+// input->select->options[no].menuitem = NULL;
+// input->select->options[no].value = Html_get_attr_wdef(html, tag, tagsize,
+// "value", NULL);
+// input->select->options[no].init_val =
+// (Html_get_attr(html, tag, tagsize, "selected") != NULL);
+// input->select->num_options++;
+// }
+// Html_stash_init(html);
+}
+
+/*
+ * ?
+ */
+static void Html_tag_close_select(DilloHtml *html, int TagIdx)
+{
+// // AL
+// DilloHtmlForm *form;
+// DilloHtmlInput *input;
+// GtkWidget *scrolledwindow;
+// DilloHtmlLB *html_lb;
+// Widget *embed_gtk;
+// GtkRequisition req;
+// int height;
+//
+// if (html->InFlags & IN_SELECT) {
+// html->InFlags &= ~IN_SELECT;
+//
+// html_lb = html->linkblock;
+//
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+// input = form->inputs->getRef (form->inputs->size() - 1);
+// if (input->type == DILLO_HTML_INPUT_SELECT) {
+// Html_option_finish(html);
+//
+// gtk_option_menu_set_menu(GTK_OPTION_MENU(input->widget),
+// input->select->menu);
+// Html_select_set_history(input);
+//
+// // gtk_option_menu_set_history(GTK_OPTION_MENU(input->widget), 1);
+//
+// gtk_widget_show(input->widget);
+//
+// embed_gtk = a_Dw_embed_gtk_new ();
+// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), input->widget);
+// DW2TB(html->dw)->addWidget (embed_gtk,
+// S_TOP(html)->style);
+// } else if (input->type == DILLO_HTML_INPUT_SEL_LIST) {
+// Html_option_finish(html);
+//
+// if (input->select->size < input->select->num_options) {
+// scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
+// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),
+// GTK_POLICY_NEVER,
+// GTK_POLICY_AUTOMATIC);
+// gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW
+// (scrolledwindow),
+// input->widget);
+//
+// gtk_container_set_focus_vadjustment
+// (GTK_CONTAINER (input->widget),
+// gtk_scrolled_window_get_vadjustment
+// (GTK_SCROLLED_WINDOW(scrolledwindow)));
+//
+// /* Calculate the height of the scrolled window */
+// gtk_widget_size_request(input->select->options[0].menuitem, &req);
+// height = input->select->size * req.height +
+// 2 * scrolledwindow->style->klass->ythickness;
+// gtk_widget_set_usize(scrolledwindow, -1, height);
+//
+// gtk_widget_show(input->widget);
+// input->widget = scrolledwindow;
+// }
+// gtk_widget_show(input->widget);
+//
+// /* note: In this next call, scrolledwindows get a warning from
+// * gdkwindow.c:422. I'm not really going to sweat it now - the
+// * embedded widget stuff is going to get massively redone anyway. */
+// embed_gtk = a_Dw_embed_gtk_new ();
+// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), input->widget);
+// DW2TB(html->dw)->addWidget (embed_gtk,
+// S_TOP(html)->style);
+// }
+// }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Set the Document Base URI
+ */
+static void Html_tag_open_base(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ DilloUrl *BaseUrl;
+
+ if (html->InFlags & IN_HEAD) {
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) {
+ BaseUrl = Html_url_new(html, attrbuf, "", 0, 0, 0, 1);
+ if (URL_SCHEME_(BaseUrl)) {
+ /* Pass the URL_SpamSafe flag to the new base url */
+ a_Url_set_flags(
+ BaseUrl, URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe);
+ a_Url_free(html->linkblock->base_url);
+ html->linkblock->base_url = BaseUrl;
+ } else {
+ MSG_HTML("base URI is relative (it MUST be absolute)\n");
+ a_Url_free(BaseUrl);
+ }
+ }
+ } else {
+ MSG_HTML("the BASE element must appear in the HEAD section\n");
+ }
+}
+
+/*
+ * <CODE>
+ */
+static void Html_tag_open_code(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+}
+
+/*
+ * <DFN>
+ */
+static void Html_tag_open_dfn(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 3);
+}
+
+/*
+ * <KBD>
+ */
+static void Html_tag_open_kbd(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+}
+
+/*
+ * <SAMP>
+ */
+static void Html_tag_open_samp(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+}
+
+/*
+ * <VAR>
+ */
+static void Html_tag_open_var(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <SUB>
+ */
+static void Html_tag_open_sub(DilloHtml *html, char *tag, int tagsize)
+{
+ HTML_SET_TOP_ATTR (html, valign, VALIGN_SUB);
+}
+
+/*
+ * <SUP>
+ */
+static void Html_tag_open_sup(DilloHtml *html, char *tag, int tagsize)
+{
+ HTML_SET_TOP_ATTR (html, valign, VALIGN_SUPER);
+}
+
+/*
+ * <DIV> (todo: make a complete implementation)
+ */
+static void Html_tag_open_div(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+ Html_tag_set_align_attr (html, tag, tagsize);
+}
+
+/*
+ * </DIV>, also used for </TABLE> and </CENTER>
+ */
+static void Html_tag_close_div(DilloHtml *html, int TagIdx)
+{
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Default close for most tags - just pop the stack.
+ */
+static void Html_tag_close_default(DilloHtml *html, int TagIdx)
+{
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Default close for paragraph tags - pop the stack and break.
+ */
+static void Html_tag_close_par(DilloHtml *html, int TagIdx)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_pop_tag(html, TagIdx);
+}
+
+
+/*
+ * Function index for the open and close functions for each tag
+ * (Alphabetically sorted for a binary search)
+ *
+ * Explanation for the 'Flags' camp:
+ *
+ * {"address", B8(010110), ...}
+ * |||||`- inline element
+ * ||||`-- block element
+ * |||`--- inline container
+ * ||`---- block container
+ * |`----- body element
+ * `------ head element
+ *
+ * Notes:
+ * - The upper two bits are not used yet.
+ * - Empty elements have both inline and block container clear.
+ * (flow have both set)
+ */
+struct _TagInfo{
+ char *name;
+ unsigned char Flags;
+ char EndTag;
+ uchar_t TagLevel;
+ TagOpenFunct open;
+ TagCloseFunct close;
+};
+
+
+const TagInfo Tags[] = {
+ {"a", B8(010101),'R',2, Html_tag_open_a, Html_tag_close_a},
+ {"abbr", B8(010101),'R',2, Html_tag_open_abbr, Html_tag_close_default},
+ /* acronym 010101 */
+ {"address", B8(010110),'R',2, Html_tag_open_address, Html_tag_close_par},
+ {"area", B8(010001),'F',0, Html_tag_open_area, Html_tag_close_default},
+ {"b", B8(010101),'R',2, Html_tag_open_b, Html_tag_close_default},
+ {"base", B8(100001),'F',0, Html_tag_open_base, Html_tag_close_default},
+ /* basefont 010001 */
+ /* bdo 010101 */
+ {"big", B8(010101),'R',2, Html_tag_open_big_small, Html_tag_close_default},
+ {"blockquote", B8(011110),'R',2,Html_tag_open_blockquote,Html_tag_close_par},
+ {"body", B8(011110),'O',1, Html_tag_open_body, Html_tag_close_body},
+ {"br", B8(010001),'F',0, Html_tag_open_br, Html_tag_close_default},
+ {"button", B8(011101),'R',2, Html_tag_open_button, Html_tag_close_default},
+ /* caption */
+ {"center", B8(011110),'R',2, Html_tag_open_center, Html_tag_close_div},
+ {"cite", B8(010101),'R',2, Html_tag_open_cite, Html_tag_close_default},
+ {"code", B8(010101),'R',2, Html_tag_open_code, Html_tag_close_default},
+ /* col 010010 'F' */
+ /* colgroup */
+ {"dd", B8(011110),'O',1, Html_tag_open_dd, Html_tag_close_par},
+ {"del", B8(011101),'R',2, Html_tag_open_strike, Html_tag_close_default},
+ {"dfn", B8(010101),'R',2, Html_tag_open_dfn, Html_tag_close_default},
+ /* dir 011010 */
+ /* todo: complete <div> support! */
+ {"div", B8(011110),'R',2, Html_tag_open_div, Html_tag_close_div},
+ {"dl", B8(011010),'R',2, Html_tag_open_dl, Html_tag_close_par},
+ {"dt", B8(010110),'O',1, Html_tag_open_dt, Html_tag_close_par},
+ {"em", B8(010101),'R',2, Html_tag_open_em, Html_tag_close_default},
+ /* fieldset */
+ {"font", B8(010101),'R',2, Html_tag_open_font, Html_tag_close_default},
+ {"form", B8(011110),'R',2, Html_tag_open_form, Html_tag_close_form},
+ {"frame", B8(010010),'F',0, Html_tag_open_frame, Html_tag_close_default},
+ {"frameset", B8(011110),'R',2,Html_tag_open_frameset, Html_tag_close_default},
+ {"h1", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h2", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h3", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h4", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h5", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h6", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"head", B8(101101),'O',1, Html_tag_open_head, Html_tag_close_head},
+ {"hr", B8(010010),'F',0, Html_tag_open_hr, Html_tag_close_default},
+ {"html", B8(001110),'O',1, Html_tag_open_html, Html_tag_close_html},
+ {"i", B8(010101),'R',2, Html_tag_open_i, Html_tag_close_default},
+ {"iframe", B8(011110),'R',2, Html_tag_open_frame, Html_tag_close_default},
+ {"img", B8(010001),'F',0, Html_tag_open_img, Html_tag_close_default},
+ {"input", B8(010001),'F',0, Html_tag_open_input, Html_tag_close_default},
+ /* ins */
+ {"isindex", B8(110001),'F',0, Html_tag_open_isindex, Html_tag_close_default},
+ {"kbd", B8(010101),'R',2, Html_tag_open_kbd, Html_tag_close_default},
+ /* label 010101 */
+ /* legend 01?? */
+ {"li", B8(011110),'O',1, Html_tag_open_li, Html_tag_close_li},
+ /* link 100000 'F' */
+ {"map", B8(011001),'R',2, Html_tag_open_map, Html_tag_close_map},
+ /* menu 1010 -- todo: not exactly 1010, it can contain LI and inline */
+ {"menu", B8(011010),'R',2, Html_tag_open_menu, Html_tag_close_par},
+ {"meta", B8(100001),'F',0, Html_tag_open_meta, Html_tag_close_default},
+ /* noframes 1011 */
+ /* noscript 1011 */
+ /* object 11xxxx */
+ {"ol", B8(011010),'R',2, Html_tag_open_ol, Html_tag_close_par},
+ /* optgroup */
+ {"option", B8(010001),'O',1, Html_tag_open_option, Html_tag_close_default},
+ {"p", B8(010110),'O',1, Html_tag_open_p, Html_tag_close_par},
+ /* param 010001 'F' */
+ {"pre", B8(010110),'R',2, Html_tag_open_pre, Html_tag_close_pre},
+ /* q 010101 */
+ {"s", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default},
+ {"samp", B8(010101),'R',2, Html_tag_open_samp, Html_tag_close_default},
+ {"script", B8(111001),'R',2, Html_tag_open_script, Html_tag_close_script},
+ {"select", B8(011001),'R',2, Html_tag_open_select, Html_tag_close_select},
+ {"small", B8(010101),'R',2, Html_tag_open_big_small, Html_tag_close_default},
+ /* span 0101 */
+ {"strike", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default},
+ {"strong", B8(010101),'R',2, Html_tag_open_strong, Html_tag_close_default},
+ {"style", B8(100101),'R',2, Html_tag_open_style, Html_tag_close_style},
+ {"sub", B8(010101),'R',2, Html_tag_open_sub, Html_tag_close_default},
+ {"sup", B8(010101),'R',2, Html_tag_open_sup, Html_tag_close_default},
+ {"table", B8(011010),'R',5, Html_tag_open_table, Html_tag_close_div},
+ /* tbody */
+ {"td", B8(011110),'O',3, Html_tag_open_td, Html_tag_close_default},
+ {"textarea", B8(010101),'R',2,Html_tag_open_textarea,Html_tag_close_textarea},
+ /* tfoot */
+ {"th", B8(011110),'O',1, Html_tag_open_th, Html_tag_close_default},
+ /* thead */
+ {"title", B8(100101),'R',2, Html_tag_open_title, Html_tag_close_title},
+ {"tr", B8(011010),'O',4, Html_tag_open_tr, Html_tag_close_default},
+ {"tt", B8(010101),'R',2, Html_tag_open_tt, Html_tag_close_default},
+ {"u", B8(010101),'R',2, Html_tag_open_u, Html_tag_close_default},
+ {"ul", B8(011010),'R',2, Html_tag_open_ul, Html_tag_close_par},
+ {"var", B8(010101),'R',2, Html_tag_open_var, Html_tag_close_default}
+
+};
+#define NTAGS (sizeof(Tags)/sizeof(Tags[0]))
+
+
+/*
+ * Compares tag from buffer ('/' or '>' or space-ended string) [p1]
+ * with tag from taglist (lowercase, zero ended string) [p2]
+ * Return value: as strcmp()
+ */
+static int Html_tag_compare(const char *p1, const char *p2)
+{
+ while ( *p2 ) {
+ if (tolower(*p1) != *p2)
+ return(tolower(*p1) - *p2);
+ ++p1;
+ ++p2;
+ }
+ return !strchr(" >/\n\r\t", *p1);
+}
+
+/*
+ * Get 'tag' index
+ * return -1 if tag is not handled yet
+ */
+static int Html_tag_index(const char *tag)
+{
+ int low, high, mid, cond;
+
+ /* Binary search */
+ low = 0;
+ high = NTAGS - 1; /* Last tag index */
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if ((cond = Html_tag_compare(tag, Tags[mid].name)) < 0 )
+ high = mid - 1;
+ else if (cond > 0)
+ low = mid + 1;
+ else
+ return mid;
+ }
+ return -1;
+}
+
+/*
+ * For elements with optional close, check whether is time to close.
+ * Return value: (1: Close, 0: Don't close)
+ * --tuned for speed.
+ */
+static int Html_needs_optional_close(int old_idx, int cur_idx)
+{
+ static int i_P = -1, i_LI, i_TD, i_TR, i_TH, i_DD, i_DT, i_OPTION;
+ // i_THEAD, i_TFOOT, i_COLGROUP;
+
+ if (i_P == -1) {
+ /* initialize the indexes of elements with optional close */
+ i_P = Html_tag_index("p"),
+ i_LI = Html_tag_index("li"),
+ i_TD = Html_tag_index("td"),
+ i_TR = Html_tag_index("tr"),
+ i_TH = Html_tag_index("th"),
+ i_DD = Html_tag_index("dd"),
+ i_DT = Html_tag_index("dt"),
+ i_OPTION = Html_tag_index("option");
+ // i_THEAD = Html_tag_index("thead");
+ // i_TFOOT = Html_tag_index("tfoot");
+ // i_COLGROUP = Html_tag_index("colgroup");
+ }
+
+ if (old_idx == i_P || old_idx == i_DT) {
+ /* P and DT are closed by block elements */
+ return (Tags[cur_idx].Flags & 2);
+ } else if (old_idx == i_LI) {
+ /* LI closes LI */
+ return (cur_idx == i_LI);
+ } else if (old_idx == i_TD || old_idx == i_TH) {
+ /* TD and TH are closed by TD, TH and TR */
+ return (cur_idx == i_TD || cur_idx == i_TH || cur_idx == i_TR);
+ } else if (old_idx == i_TR) {
+ /* TR closes TR */
+ return (cur_idx == i_TR);
+ } else if (old_idx == i_DD) {
+ /* DD is closed by DD and DT */
+ return (cur_idx == i_DD || cur_idx == i_DT);
+ } else if (old_idx == i_OPTION) {
+ return 1; // OPTION always needs close
+ }
+
+ /* HTML, HEAD, BODY are handled by Html_test_section(), not here. */
+ /* todo: TBODY is pending */
+ return 0;
+}
+
+
+/*
+ * Conditional cleanup of the stack (at open time).
+ * - This helps catching block elements inside inline containers (a BUG).
+ * - It also closes elements with "optional" close tag.
+ *
+ * This function is called when opening a block element or <OPTION>.
+ *
+ * It searches the stack closing open inline containers, and closing
+ * elements with optional close tag when necessary.
+ *
+ * Note: OPTION is the only non-block element with an optional close.
+ */
+static void Html_stack_cleanup_at_open(DilloHtml *html, int new_idx)
+{
+ /* We know that the element we're about to push is a block element.
+ * (except for OPTION, which is an empty inline, so is closed anyway)
+ * Notes:
+ * Its 'tag' is not yet pushed into the stack,
+ * 'new_idx' is its index inside Tags[].
+ */
+
+ if (!html->TagSoup)
+ return;
+
+ while (html->stack->size() > 1) {
+ int oldtag_idx = S_TOP(html)->tag_idx;
+
+ if (Tags[oldtag_idx].EndTag == 'O') { // Element with optional close
+ if (!Html_needs_optional_close(oldtag_idx, new_idx))
+ break;
+ } else if (Tags[oldtag_idx].Flags & 8) { // Block container
+ break;
+ }
+
+ /* we have an inline (or empty) container... */
+ if (Tags[oldtag_idx].EndTag == 'R') {
+ MSG_HTML("<%s> is not allowed to contain <%s>. -- closing <%s>\n",
+ Tags[oldtag_idx].name, Tags[new_idx].name,
+ Tags[oldtag_idx].name);
+ }
+
+ /* Workaround for Apache and its bad HTML directory listings... */
+ if ((html->InFlags & IN_PRE) &&
+ strcmp(Tags[new_idx].name, "hr") == 0)
+ break;
+
+ /* This call closes the top tag only. */
+ Html_tag_cleanup_at_close(html, oldtag_idx);
+ }
+}
+
+/*
+ * HTML, HEAD and BODY elements have optional open and close tags.
+ * Handle this "magic" here.
+ */
+static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag)
+{
+ char *tag;
+ int tag_idx;
+
+ if (!(html->InFlags & IN_HTML) && html->DocType == DT_NONE)
+ MSG_HTML("the required DOCTYPE declaration is missing (or invalid)\n");
+
+ if (!(html->InFlags & IN_HTML)) {
+ tag = "<html>";
+ tag_idx = Html_tag_index(tag + 1);
+ if (tag_idx != new_idx || IsCloseTag) {
+ /* implicit open */
+ Html_force_push_tag(html, tag_idx);
+ Tags[tag_idx].open (html, tag, strlen(tag));
+ }
+ }
+
+ if (Tags[new_idx].Flags & 32) {
+ /* head element */
+ if (!(html->InFlags & IN_HEAD)) {
+ tag = "<head>";
+ tag_idx = Html_tag_index(tag + 1);
+ if (tag_idx != new_idx || IsCloseTag) {
+ /* implicit open of the head element */
+ Html_force_push_tag(html, tag_idx);
+ Tags[tag_idx].open (html, tag, strlen(tag));
+ }
+ }
+
+ } else if (Tags[new_idx].Flags & 16) {
+ /* body element */
+ if (html->InFlags & IN_HEAD) {
+ tag = "</head>";
+ tag_idx = Html_tag_index(tag + 2);
+ Tags[tag_idx].close (html, tag_idx);
+ }
+ tag = "<body>";
+ tag_idx = Html_tag_index(tag + 1);
+ if (tag_idx != new_idx || IsCloseTag) {
+ /* implicit open */
+ Html_force_push_tag(html, tag_idx);
+ Tags[tag_idx].open (html, tag, strlen(tag));
+ }
+ }
+}
+
+/*
+ * Process a tag, given as 'tag' and 'tagsize'. -- tagsize is [1 based]
+ * ('tag' must include the enclosing angle brackets)
+ * This function calls the right open or close function for the tag.
+ */
+static void Html_process_tag(DilloHtml *html, char *tag, int tagsize)
+{
+ int ci, ni; /* current and new tag indexes */
+ const char *attrbuf;
+ char *start = tag + 1; /* discard the '<' */
+ int IsCloseTag = (*start == '/');
+
+ ni = Html_tag_index(start + IsCloseTag);
+
+ /* todo: doctype parsing is a bit fuzzy, but enough for the time being */
+ if (ni == -1 && !(html->InFlags & IN_HTML)) {
+ if (tagsize > 9 && !dStrncasecmp(tag, "<!doctype", 9))
+ Html_parse_doctype(html, tag, tagsize);
+ }
+
+ if (!(html->InFlags & IN_HTML)) {
+ _MSG("\nDoctype: %f\n\n", html->DocTypeVersion);
+ }
+
+ /* Handle HTML, HEAD and BODY. Elements with optional open and close */
+ if (ni != -1 && !(html->InFlags & IN_BODY) /* && parsing HTML */)
+ Html_test_section(html, ni, IsCloseTag);
+
+ /* White space handling */
+ if (html->SPCPending && (!SGML_SPCDEL || !IsCloseTag))
+ /* SGML_SPCDEL requires space pending and open tag */
+ DW2TB(html->dw)->addSpace(S_TOP(html)->style);
+ html->SPCPending = FALSE;
+
+ /* Tag processing */
+ ci = S_TOP(html)->tag_idx;
+ if (ni != -1) {
+
+ if (!IsCloseTag) {
+ /* Open function */
+
+ /* Cleanup when opening a block element, or
+ * when openning over an element with optional close */
+ if (Tags[ni].Flags & 2 || (ci != -1 && Tags[ci].EndTag == 'O'))
+ Html_stack_cleanup_at_open(html, ni);
+
+ /* todo: this is only raising a warning, take some defined action.
+ * Note: apache uses IMG inside PRE (we could use its "alt"). */
+ if ((html->InFlags & IN_PRE) && Html_tag_pre_excludes(ni))
+ MSG_HTML("<pre> is not allowed to contain <%s>\n", Tags[ni].name);
+
+ /* Push the tag into the stack */
+ Html_push_tag(html, ni);
+
+ /* Call the open function for this tag */
+ Tags[ni].open (html, tag, tagsize);
+
+ /* Now parse attributes that can appear on any tag */
+ if (tagsize >= 8 && /* length of "<t id=i>" */
+ (attrbuf = Html_get_attr2(html, tag, tagsize, "id",
+ HTML_LeftTrim | HTML_RightTrim))) {
+ /* According to the SGML declaration of HTML 4, all NAME values
+ * occuring outside entities must be converted to uppercase
+ * (this is what "NAMECASE GENERAL YES" says). But the HTML 4
+ * spec states in Sec. 7.5.2 that anchor ids are case-sensitive.
+ * So we don't do it and hope for better specs in the future ...
+ */
+ Html_check_name_val(html, attrbuf, "id");
+ /* We compare the "id" value with the url-decoded "name" value */
+ if (!html->NameVal || strcmp(html->NameVal, attrbuf)) {
+ if (html->NameVal)
+ MSG_HTML("'id' and 'name' attribute of <a> tag differ\n");
+ Html_add_anchor(html, attrbuf);
+ }
+ }
+
+ /* Reset NameVal */
+ if (html->NameVal) {
+ dFree(html->NameVal);
+ html->NameVal = NULL;
+ }
+
+ /* let the parser know this was an open tag */
+ html->PrevWasOpenTag = TRUE;
+
+ /* Request inmediate close for elements with forbidden close tag. */
+ /* todo: XHTML always requires close tags. A simple implementation
+ * of the commented clause below will make it work. */
+ if (/* parsing HTML && */ Tags[ni].EndTag == 'F')
+ html->ReqTagClose = TRUE;
+ }
+
+ /* Close function: test for </x>, ReqTagClose, <x /> and <x/> */
+ if (*start == '/' || /* </x> */
+ html->ReqTagClose || /* request */
+ (tag[tagsize - 2] == '/' && /* XML: */
+ (isspace(tag[tagsize - 3]) || /* <x /> */
+ (size_t)tagsize == strlen(Tags[ni].name) + 3))) { /* <x/> */
+
+ Tags[ni].close (html, ni);
+ /* This was a close tag */
+ html->PrevWasOpenTag = FALSE;
+ html->ReqTagClose = FALSE;
+ }
+
+ } else {
+ /* tag not working - just ignore it */
+ }
+}
+
+/*
+ * Get attribute value for 'attrname' and return it.
+ * Tags start with '<' and end with a '>' (Ex: "<P align=center>")
+ * tagsize = strlen(tag) from '<' to '>', inclusive.
+ *
+ * Returns one of the following:
+ * * The value of the attribute.
+ * * An empty string if the attribute exists but has no value.
+ * * NULL if the attribute doesn't exist.
+ */
+static const char *Html_get_attr2(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname,
+ int tag_parsing_flags)
+{
+ int i, isocode, entsize, Found = 0, delimiter = 0, attr_pos = 0;
+ Dstr *Buf = html->attr_data;
+ DilloHtmlTagParsingState state = SEEK_ATTR_START;
+
+ dReturn_val_if_fail(*attrname, NULL);
+
+ dStr_truncate(Buf, 0);
+
+ for (i = 1; i < tagsize; ++i) {
+ switch (state) {
+ case SEEK_ATTR_START:
+ if (isspace(tag[i]))
+ state = SEEK_TOKEN_START;
+ else if (tag[i] == '=')
+ state = SEEK_VALUE_START;
+ break;
+
+ case MATCH_ATTR_NAME:
+ if ((Found = (!(attrname[attr_pos]) &&
+ (tag[i] == '=' || isspace(tag[i]) || tag[i] == '>')))) {
+ state = SEEK_TOKEN_START;
+ --i;
+ } else if (tolower(tag[i]) != tolower(attrname[attr_pos++]))
+ state = SEEK_ATTR_START;
+ break;
+
+ case SEEK_TOKEN_START:
+ if (tag[i] == '=') {
+ state = SEEK_VALUE_START;
+ } else if (!isspace(tag[i])) {
+ attr_pos = 0;
+ state = (Found) ? FINISHED : MATCH_ATTR_NAME;
+ --i;
+ }
+ break;
+ case SEEK_VALUE_START:
+ if (!isspace(tag[i])) {
+ delimiter = (tag[i] == '"' || tag[i] == '\'') ? tag[i] : ' ';
+ i -= (delimiter == ' ');
+ state = (Found) ? GET_VALUE : SKIP_VALUE;
+ }
+ break;
+
+ case SKIP_VALUE:
+ if ((delimiter == ' ' && isspace(tag[i])) || tag[i] == delimiter)
+ state = SEEK_TOKEN_START;
+ break;
+ case GET_VALUE:
+ if ((delimiter == ' ' && (isspace(tag[i]) || tag[i] == '>')) ||
+ tag[i] == delimiter) {
+ state = FINISHED;
+ } else if (tag[i] == '&' &&
+ (tag_parsing_flags & HTML_ParseEntities)) {
+ if ((isocode = Html_parse_entity(html, tag+i,
+ tagsize-i, &entsize)) >= 0) {
+ if (isocode >= 128) {
+ char buf[4];
+ int k, n = utf8encode(isocode, buf);
+ for (k = 0; k < n; ++k)
+ dStr_append_c(Buf, buf[k]);
+ } else {
+ dStr_append_c(Buf, (char) isocode);
+ }
+ i += entsize-1;
+ } else {
+ dStr_append_c(Buf, tag[i]);
+ }
+ } else if (tag[i] == '\r' || tag[i] == '\t') {
+ dStr_append_c(Buf, ' ');
+ } else if (tag[i] == '\n') {
+ /* ignore */
+ } else {
+ dStr_append_c(Buf, tag[i]);
+ }
+ break;
+
+ case FINISHED:
+ i = tagsize;
+ break;
+ }
+ }
+
+ if (tag_parsing_flags & HTML_LeftTrim)
+ while (isspace(Buf->str[0]))
+ dStr_erase(Buf, 0, 1);
+ if (tag_parsing_flags & HTML_RightTrim)
+ while (Buf->len && isspace(Buf->str[Buf->len - 1]))
+ dStr_truncate(Buf, Buf->len - 1);
+
+ return (Found) ? Buf->str : NULL;
+}
+
+/*
+ * Call Html_get_attr2 telling it to parse entities and strip the result
+ */
+static const char *Html_get_attr(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname)
+{
+ return Html_get_attr2(html, tag, tagsize, attrname,
+ HTML_LeftTrim | HTML_RightTrim | HTML_ParseEntities);
+}
+
+/*
+ * "Html_get_attr with default"
+ * Call Html_get_attr() and strdup() the returned string.
+ * If the attribute isn't found a copy of 'def' is returned.
+ */
+static char *Html_get_attr_wdef(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname,
+ const char *def)
+{
+ const char *attrbuf = Html_get_attr(html, tag, tagsize, attrname);
+
+ return attrbuf ? dStrdup(attrbuf) : dStrdup(def);
+}
+
+/*
+ * Add a widget to the page.
+ */
+static void Html_add_widget(DilloHtml *html,
+ Widget *widget,
+ char *width_str,
+ char *height_str,
+ StyleAttrs *style_attrs)
+{
+ StyleAttrs new_style_attrs;
+ Style *style;
+
+ new_style_attrs = *style_attrs;
+ new_style_attrs.width = width_str ?
+ Html_parse_length (html, width_str) : LENGTH_AUTO;
+ new_style_attrs.height = height_str ?
+ Html_parse_length (html, height_str) : LENGTH_AUTO;
+ style = Style::create (HT2LT(html), &new_style_attrs);
+ DW2TB(html->dw)->addWidget (widget, style);
+ style->unref ();
+}
+
+
+/*
+ * Dispatch the apropriate function for 'Op'
+ * This function is a Cache client and gets called whenever new data arrives
+ * Op : operation to perform.
+ * CbData : a pointer to a DilloHtml structure
+ * Buf : a pointer to new data
+ * BufSize : new data size (in bytes)
+ */
+static void Html_callback(int Op, CacheClient_t *Client)
+{
+ if (Op) {
+ Html_write((DilloHtml*)Client->CbData, (char*)Client->Buf,
+ Client->BufSize, 1);
+ Html_close((DilloHtml*)Client->CbData, Client->Key);
+ } else {
+ Html_write((DilloHtml*)Client->CbData, (char*)Client->Buf,
+ Client->BufSize, 0);
+ }
+}
+
+/*
+ * Here's where we parse the html and put it into the Textblock structure.
+ * Return value: number of bytes parsed
+ */
+static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof)
+{
+ char ch = 0, *p, *text;
+ Textblock *textblock;
+ int token_start, buf_index;
+
+ dReturn_val_if_fail ((textblock = DW2TB(html->dw)) != NULL, 0);
+
+ /* Now, 'buf' and 'bufsize' define a buffer aligned to start at a token
+ * boundary. Iterate through tokens until end of buffer is reached. */
+ buf_index = 0;
+ token_start = buf_index;
+ while (buf_index < bufsize) {
+ /* invariant: buf_index == bufsize || token_start == buf_index */
+
+ if (S_TOP(html)->parse_mode ==
+ DILLO_HTML_PARSE_MODE_VERBATIM) {
+ /* Non HTML code here, let's skip until closing tag */
+ do {
+ char *tag = S_TOP(html)->tag_name;
+ buf_index += strcspn(buf + buf_index, "<");
+ if (buf_index + (int)strlen(tag) + 3 > bufsize) {
+ buf_index = bufsize;
+ } else if (strncmp(buf + buf_index, "</", 2) == 0 &&
+ Html_match_tag(tag, buf+buf_index+2, strlen(tag)+1)) {
+ /* copy VERBATIM text into the stash buffer */
+ text = dStrndup(buf + token_start, buf_index - token_start);
+ dStr_append(html->Stash, text);
+ dFree(text);
+ token_start = buf_index;
+ break;
+ } else
+ ++buf_index;
+ } while (buf_index < bufsize);
+ }
+
+ if (isspace(buf[buf_index])) {
+ /* whitespace: group all available whitespace */
+ while (++buf_index < bufsize && isspace(buf[buf_index]));
+ Html_process_space(html, buf + token_start, buf_index - token_start);
+ token_start = buf_index;
+
+ } else if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) &&
+ (isalpha(ch) || strchr("/!?", ch)) ) {
+ /* Tag */
+ if (buf_index + 3 < bufsize && !strncmp(buf + buf_index, "<!--", 4)) {
+ /* Comment: search for close of comment, skipping over
+ * everything except a matching "-->" tag. */
+ while ( (p = (char*) memchr(buf + buf_index, '>',
+ bufsize - buf_index)) ){
+ buf_index = p - buf + 1;
+ if (p[-1] == '-' && p[-2] == '-') break;
+ }
+ if (p) {
+ /* Got the whole comment. Let's throw it away! :) */
+ token_start = buf_index;
+ } else
+ buf_index = bufsize;
+ } else {
+ /* Tag: search end of tag (skipping over quoted strings) */
+ html->CurrTagOfs = html->Start_Ofs + token_start;
+
+ while ( buf_index < bufsize ) {
+ buf_index++;
+ buf_index += strcspn(buf + buf_index, ">\"'<");
+ if ((ch = buf[buf_index]) == '>') {
+ break;
+ } else if (ch == '"' || ch == '\'') {
+ /* Skip over quoted string */
+ buf_index++;
+ buf_index += strcspn(buf + buf_index,
+ (ch == '"') ? "\">" : "'>");
+ if (buf[buf_index] == '>') {
+ /* Unterminated string value? Let's look ahead and test:
+ * (<: unterminated, closing-quote: terminated) */
+ int offset = buf_index + 1;
+ offset += strcspn(buf + offset,
+ (ch == '"') ? "\"<" : "'<");
+ if (buf[offset] == ch || !buf[offset]) {
+ buf_index = offset;
+ } else {
+ MSG_HTML("attribute lacks closing quote\n");
+ break;
+ }
+ }
+ } else if (ch == '<') {
+ /* unterminated tag detected */
+ p = dStrndup(buf+token_start+1,
+ strcspn(buf+token_start+1, " <"));
+ MSG_HTML("<%s> element lacks its closing '>'\n", p);
+ dFree(p);
+ --buf_index;
+ break;
+ }
+ }
+ if (buf_index < bufsize) {
+ buf_index++;
+ Html_process_tag(html, buf + token_start,
+ buf_index - token_start);
+ token_start = buf_index;
+ }
+ }
+ } else {
+ /* A Word: search for whitespace or tag open */
+ while (++buf_index < bufsize) {
+ buf_index += strcspn(buf + buf_index, " <\n\r\t\f\v");
+ if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) &&
+ !isalpha(ch) && !strchr("/!?", ch))
+ continue;
+ break;
+ }
+ if (buf_index < bufsize || Eof) {
+ /* successfully found end of token */
+ Html_process_word(html, buf + token_start,
+ buf_index - token_start);
+ token_start = buf_index;
+ }
+ }
+ }/*while*/
+
+ textblock->flush ();
+
+ return token_start;
+}
+
+/*
+ * Process the newly arrived html and put it into the page structure.
+ * (This function is called by Html_callback whenever there's new data)
+ */
+static void Html_write(DilloHtml *html, char *Buf, int BufSize, int Eof)
+{
+ int token_start;
+ char *buf = Buf + html->Start_Ofs;
+ int bufsize = BufSize - html->Start_Ofs;
+
+ dReturn_if_fail (DW2TB(html->dw) != NULL);
+
+ html->Start_Buf = Buf;
+ token_start = Html_write_raw(html, buf, bufsize, Eof);
+ html->Start_Ofs += token_start;
+
+ if (html->bw)
+ a_UIcmd_set_page_prog(html->bw, html->Start_Ofs, 1);
+}
+
+/*
+ * Finish parsing a HTML page
+ * (Free html struct, close the client and update the page progress bar).
+ */
+static void Html_close(DilloHtml *html, int ClientKey)
+{
+ int si;
+
+ //#if defined (DEBUG_LEVEL) && DEBUG_LEVEL >= 1
+ //a_Dw_widget_print_tree (GTK_DW_VIEWPORT(html->dw->viewport)->child);
+ //#endif
+
+ /* force the close of elements left open (todo: not for XHTML) */
+ while ((si = html->stack->size() - 1)) {
+ if (html->stack->getRef(si)->tag_idx != -1) {
+ Html_tag_cleanup_at_close(html, html->stack->getRef(si)->tag_idx);
+ }
+ }
+ dFree(html->stack->getRef(0)->tag_name); /* "none" */
+ (html->stack->getRef(0)->style)->unref (); /* template style */
+
+ delete (html->stack);
+
+ dStr_free(html->Stash, TRUE);
+ dFree(html->SPCBuf);
+ dStr_free(html->attr_data, TRUE);
+
+ /* Remove this client from our active list */
+ a_Bw_close_client(html->bw, ClientKey);
+
+ /* Set progress bar insensitive */
+ a_UIcmd_set_page_prog(html->bw, 0, 0);
+ dFree(html);
+}
+
+