/* * This file is part of NetSurf, http://netsurf-browser.org/ * Licensed under the GNU General Public License, * http://www.opensource.org/licenses/gpl-license * Copyright 2005 James Bursa * Copyright 2003 Phil Mellor * Copyright 2005 John M Bell * Copyright 2006 Richard Wilson */ /** \file * Conversion of XML tree to box tree (implementation). */ #define _GNU_SOURCE /* for strndup */ #include #include #include #include #include #include #include #include #include #include "utils/config.h" #include "content/content.h" #include "css/css.h" #include "desktop/browser.h" #include "desktop/options.h" #include "render/box.h" #include "render/form.h" #include "render/html.h" #ifdef riscos #include "desktop/gui.h" #endif //#define NDEBUG #include "utils/log.h" #include "utils/messages.h" #include "utils/talloc.h" #include "utils/url.h" #include "utils/utils.h" static const content_type image_types[] = { #ifdef WITH_JPEG CONTENT_JPEG, #endif #ifdef WITH_GIF CONTENT_GIF, #endif #ifdef WITH_BMP CONTENT_BMP, #endif #ifdef WITH_MNG CONTENT_PNG, CONTENT_JNG, CONTENT_MNG, #endif #ifdef WITH_SPRITE CONTENT_SPRITE, #endif #ifdef WITH_DRAW CONTENT_DRAW, #endif #ifdef WITH_ARTWORKS CONTENT_ARTWORKS, #endif CONTENT_UNKNOWN }; #define MAX_SPAN (100) /* the strings are not important, since we just compare the pointers */ const char *TARGET_SELF = "_self"; const char *TARGET_PARENT = "_parent"; const char *TARGET_TOP = "_top"; const char *TARGET_BLANK = "_blank"; static bool convert_xml_to_box(xmlNode *n, struct content *content, struct css_style *parent_style, struct box *parent, struct box **inline_container, char *href, const char *target, char *title); bool box_construct_element(xmlNode *n, struct content *content, struct css_style *parent_style, struct box *parent, struct box **inline_container, char *href, const char *target, char *title); bool box_construct_text(xmlNode *n, struct content *content, struct css_style *parent_style, struct box *parent, struct box **inline_container, char *href, const char *target, char *title); static struct css_style * box_get_style(struct content *c, struct css_style *parent_style, xmlNode *n); static void box_solve_display(struct css_style *style, bool root); static void box_set_cellpadding(struct box *box, int value); static void box_set_table_border(struct box *box, int value, colour color); static void box_text_transform(char *s, unsigned int len, css_text_transform tt); #define BOX_SPECIAL_PARAMS xmlNode *n, struct content *content, \ struct box *box, bool *convert_children static bool box_a(BOX_SPECIAL_PARAMS); static bool box_body(BOX_SPECIAL_PARAMS); static bool box_br(BOX_SPECIAL_PARAMS); static bool box_image(BOX_SPECIAL_PARAMS); static bool box_form(BOX_SPECIAL_PARAMS); static bool box_textarea(BOX_SPECIAL_PARAMS); static bool box_select(BOX_SPECIAL_PARAMS); static bool box_input(BOX_SPECIAL_PARAMS); static bool box_input_text(BOX_SPECIAL_PARAMS, bool password); static bool box_button(BOX_SPECIAL_PARAMS); static bool box_frameset(BOX_SPECIAL_PARAMS); static bool box_create_frameset(struct content_html_frames *f, xmlNode *n, struct content *content); static bool box_select_add_option(struct form_control *control, xmlNode *n); static bool box_object(BOX_SPECIAL_PARAMS); static bool box_embed(BOX_SPECIAL_PARAMS); static bool box_pre(BOX_SPECIAL_PARAMS); /*static bool box_applet(BOX_SPECIAL_PARAMS);*/ static bool box_iframe(BOX_SPECIAL_PARAMS); static bool box_get_attribute(xmlNode *n, const char *attribute, void *context, char **value); static struct frame_dimension *box_parse_multi_lengths(const char *s, unsigned int *count); /* element_table must be sorted by name */ struct element_entry { char name[10]; /* element type */ bool (*convert)(BOX_SPECIAL_PARAMS); }; static const struct element_entry element_table[] = { {"a", box_a}, /* {"applet", box_applet},*/ {"body", box_body}, {"br", box_br}, {"button", box_button}, {"embed", box_embed}, {"form", box_form}, {"frameset", box_frameset}, {"iframe", box_iframe}, {"image", box_image}, {"img", box_image}, {"input", box_input}, {"object", box_object}, {"pre", box_pre}, {"select", box_select}, {"textarea", box_textarea} }; #define ELEMENT_TABLE_COUNT (sizeof(element_table) / sizeof(element_table[0])) /** * Construct a box tree from an xml tree and stylesheets. * * \param n xml tree * \param c content of type CONTENT_HTML to construct box tree in * \return true on success, false on memory exhaustion */ bool xml_to_box(xmlNode *n, struct content *c) { struct box root; struct box *inline_container = 0; assert(c->type == CONTENT_HTML); root.type = BOX_BLOCK; root.style = NULL; root.next = NULL; root.prev = NULL; root.children = NULL; root.last = NULL; root.parent = NULL; root.float_children = NULL; root.next_float = NULL; c->data.html.style = talloc_memdup(c, &css_base_style, sizeof css_base_style); if (!c->data.html.style) return false; c->data.html.style->font_size.value.length.value = option_font_size * 0.1; /* and get the default font family from the options */ c->data.html.style->font_family = option_font_default; c->data.html.object_count = 0; c->data.html.object = 0; if (!convert_xml_to_box(n, c, c->data.html.style, &root, &inline_container, 0, 0, 0)) return false; if (!box_normalise_block(&root, c)) return false; c->data.html.layout = root.children; c->data.html.layout->parent = NULL; return true; } /* mapping from CSS display to box type * this table must be in sync with css/css_enums */ static const box_type box_map[] = { 0, /*CSS_DISPLAY_INHERIT,*/ BOX_INLINE, /*CSS_DISPLAY_INLINE,*/ BOX_BLOCK, /*CSS_DISPLAY_BLOCK,*/ BOX_BLOCK, /*CSS_DISPLAY_LIST_ITEM,*/ BOX_INLINE, /*CSS_DISPLAY_RUN_IN,*/ BOX_INLINE_BLOCK, /*CSS_DISPLAY_INLINE_BLOCK,*/ BOX_TABLE, /*CSS_DISPLAY_TABLE,*/ BOX_TABLE, /*CSS_DISPLAY_INLINE_TABLE,*/ BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_ROW_GROUP,*/ BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_HEADER_GROUP,*/ BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_FOOTER_GROUP,*/ BOX_TABLE_ROW, /*CSS_DISPLAY_TABLE_ROW,*/ BOX_INLINE, /*CSS_DISPLAY_TABLE_COLUMN_GROUP,*/ BOX_INLINE, /*CSS_DISPLAY_TABLE_COLUMN,*/ BOX_TABLE_CELL, /*CSS_DISPLAY_TABLE_CELL,*/ BOX_INLINE /*CSS_DISPLAY_TABLE_CAPTION,*/ }; /** * Recursively construct a box tree from an xml tree and stylesheets. * * \param n fragment of xml tree * \param content content of type CONTENT_HTML that is being processed * \param parent_style style at this point in xml tree * \param parent parent in box tree * \param inline_container current inline container box, or 0, updated to * new current inline container on exit * \param href current link URL, or 0 if not in a link * \param target current link target, or 0 if none * \param title current title, or 0 if none * \return true on success, false on memory exhaustion */ bool convert_xml_to_box(xmlNode *n, struct content *content, struct css_style *parent_style, struct box *parent, struct box **inline_container, char *href, const char *target, char *title) { switch (n->type) { case XML_ELEMENT_NODE: return box_construct_element(n, content, parent_style, parent, inline_container, href, target, title); case XML_TEXT_NODE: return box_construct_text(n, content, parent_style, parent, inline_container, href, target, title); default: /* not an element or text node: ignore it (eg. comment) */ return true; } } /** * Construct the box tree for an XML element. * * \param n XML node of type XML_ELEMENT_NODE * \param content content of type CONTENT_HTML that is being processed * \param parent_style style at this point in xml tree * \param parent parent in box tree * \param inline_container current inline container box, or 0, updated to * new current inline container on exit * \param href current link URL, or 0 if not in a link * \param target current link target, or 0 if none * \param title current title, or 0 if none * \return true on success, false on memory exhaustion */ bool box_construct_element(xmlNode *n, struct content *content, struct css_style *parent_style, struct box *parent, struct box **inline_container, char *href, const char *target, char *title) { bool convert_children = true; char *id = 0; char *s; struct box *box = 0; struct box *inline_container_c; struct box *inline_end; struct css_style *style = 0; struct element_entry *element; colour border_color; xmlChar *title0; xmlNode *c; assert(n); assert(n->type == XML_ELEMENT_NODE); assert(parent_style); assert(parent); assert(inline_container); gui_multitask(); /* In case the parent is a pre block, we clear the * strip_leading_newline flag since it is not used if we * follow the pre with a tag */ parent->strip_leading_newline = 0; style = box_get_style(content, parent_style, n); if (!style) return false; /* extract title attribute, if present */ if ((title0 = xmlGetProp(n, (const xmlChar *) "title"))) { char *title1 = squash_whitespace(title0); xmlFree(title0); if (!title1) return false; title = talloc_strdup(content, title1); free(title1); if (!title) return false; } /* extract id attribute, if present */ if (!box_get_attribute(n, "id", content, &id)) return false; /* create box for this element */ box = box_create(style, href, target, title, id, content); if (!box) return false; /* set box type from style */ box->type = box_map[style->display]; /* special elements */ element = bsearch((const char *) n->name, element_table, ELEMENT_TABLE_COUNT, sizeof(element_table[0]), (int (*)(const void *, const void *)) strcmp); if (element) { /* a special convert function exists for this element */ if (!element->convert(n, content, box, &convert_children)) return false; href = box->href; target = box->target; } if (style->display == CSS_DISPLAY_NONE) { talloc_free(style); /* We can't do this, as it will destroy any gadget * associated with the box, thus making any form usage * access freed memory. The box is in the talloc context, * anyway, so will get cleaned up with the content. */ /* box_free_box(box); */ return true; } if (!*inline_container && (box->type == BOX_INLINE || box->type == BOX_BR || box->type == BOX_INLINE_BLOCK || style->float_ == CSS_FLOAT_LEFT || style->float_ == CSS_FLOAT_RIGHT)) { /* this is the first inline in a block: make a container */ *inline_container = box_create(0, 0, 0, 0, 0, content); if (!*inline_container) return false; (*inline_container)->type = BOX_INLINE_CONTAINER; box_add_child(parent, *inline_container); } if (box->type == BOX_INLINE || box->type == BOX_BR) { /* inline box: add to tree and recurse */ box_add_child(*inline_container, box); if (convert_children && n->children) { for (c = n->children; c; c = c->next) if (!convert_xml_to_box(c, content, style, parent, inline_container, href, target, title)) return false; inline_end = box_create(style, href, target, title, id, content); if (!inline_end) return false; inline_end->type = BOX_INLINE_END; if (*inline_container) box_add_child(*inline_container, inline_end); else box_add_child(box->parent, inline_end); box->inline_end = inline_end; inline_end->inline_end = box; } } else if (box->type == BOX_INLINE_BLOCK) { /* inline block box: add to tree and recurse */ box_add_child(*inline_container, box); inline_container_c = 0; for (c = n->children; convert_children && c; c = c->next) if (!convert_xml_to_box(c, content, style, box, &inline_container_c, href, target, title)) return false; } else { if (style->float_ == CSS_FLOAT_LEFT || style->float_ == CSS_FLOAT_RIGHT) { /* float: insert a float box between the parent and * current node */ parent = box_create(0, href, target, title, 0, content); if (!parent) return false; if (style->float_ == CSS_FLOAT_LEFT) parent->type = BOX_FLOAT_LEFT; else parent->type = BOX_FLOAT_RIGHT; box_add_child(*inline_container, parent); } if (style->display == CSS_DISPLAY_LIST_ITEM) { struct box *marker; marker = box_create(style, 0, 0, title, 0, content); if (!marker) return false; marker->type = BOX_BLOCK; /** \todo marker content (list-style-type) */ switch (style->list_style_type) { case CSS_LIST_STYLE_TYPE_DISC: default: /* 2022 BULLET */ marker->text = "\342\200\242"; marker->length = 3; break; case CSS_LIST_STYLE_TYPE_CIRCLE: /* 25CB WHITE CIRCLE */ marker->text = "\342\227\213"; marker->length = 3; break; case CSS_LIST_STYLE_TYPE_SQUARE: /* 25AA BLACK SMALL SQUARE */ marker->text = "\342\226\252"; marker->length = 3; break; case CSS_LIST_STYLE_TYPE_DECIMAL: case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: if (parent->last && parent->last->list_marker) marker->rows = parent->last-> list_marker->rows + 1; marker->text = talloc_array(content, char, 20); if (!marker->text) return false; snprintf(marker->text, 20, "%u.", marker->rows); marker->length = strlen(marker->text); break; case CSS_LIST_STYLE_TYPE_NONE: marker->text = 0; marker->length = 0; break; } if (style->list_style_image.type == CSS_LIST_STYLE_IMAGE_URI) { if (!html_fetch_object(content, style->list_style_image.uri, marker, 0, content->available_width, 1000, false)) return false; } box->list_marker = marker; marker->parent = box; } /* non-inline box: add to tree and recurse */ box_add_child(parent, box); inline_container_c = 0; for (c = n->children; convert_children && c; c = c->next) if (!convert_xml_to_box(c, content, style, box, &inline_container_c, href, target, title)) return false; if (style->float_ == CSS_FLOAT_NONE) /* new inline container unless this is a float */ *inline_container = 0; } /* misc. attributes that can't be handled in box_get_style() */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "colspan"))) { if (isdigit(s[0])) { box->columns = strtol(s, NULL, 10); if ((MAX_SPAN < box->columns) || (box->columns < 1)) box->columns = 1; } xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "rowspan"))) { if (isdigit(s[0])) { box->rows = strtol(s, NULL, 10); if ((MAX_SPAN < box->rows) || (box->rows < 1)) box->rows = 1; } xmlFree(s); } if (strcmp((const char *) n->name, "table") == 0) { border_color = 0x888888; /* default colour */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "cellpadding"))) { char *endp; long value = strtol(s, &endp, 10); if (*endp == 0 && 0 <= value && value < 1000) /* % not implemented */ box_set_cellpadding(box, value); xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "bordercolor"))) { unsigned int r, g, b; if (s[0] == '#' && sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) border_color = (b << 16) | (g << 8) | r; else if (s[0] != '#') border_color = named_colour(s); xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "border"))) { int value = atoi(s); if (!strrchr(s, '%') && 0 < value) /* % not implemented */ box_set_table_border(box, value, border_color); xmlFree(s); } } /* transfer down to the elements */ if (strcmp((const char *) n->name, "tr") == 0) { if ((s = (char *) xmlGetProp(n, (const xmlChar *) "height"))) { float value = atof(s); if (value < 0 || strlen(s) == 0) { /* ignore negative values and height="" */ } else if (strrchr(s, '%')) { /* the specification doesn't make clear what * percentage heights mean, so ignore them */ } else { /* The tree is not normalized yet, so accept cells not * in rows and rows not in row groups. */ struct box *child; float current; for (child = box->children; child; child = child->next) { if (child->type == BOX_TABLE_CELL) { current = css_len2px( &child->style->height.length, child->style); value = (value > current) ? value : current; child->style->height.height = CSS_HEIGHT_LENGTH; child->style->height.length.unit = CSS_UNIT_PX; child->style->height.length.value = value; } } } xmlFree(s); } } /* fetch any background image for this box */ if (style->background_image.type == CSS_BACKGROUND_IMAGE_URI) { if (!html_fetch_object(content, style->background_image.uri, box, image_types, content->available_width, 1000, true)) return false; } return true; } /** * Construct the box tree for an XML text node. * * \param n XML node of type XML_TEXT_NODE * \param content content of type CONTENT_HTML that is being processed * \param parent_style style at this point in xml tree * \param parent parent in box tree * \param inline_container current inline container box, or 0, updated to * new current inline container on exit * \param href current link URL, or 0 if not in a link * \param target current link target, or 0 if none * \param title current title, or 0 if none * \return true on success, false on memory exhaustion */ bool box_construct_text(xmlNode *n, struct content *content, struct css_style *parent_style, struct box *parent, struct box **inline_container, char *href, const char *target, char *title) { struct box *box = 0; assert(n); assert(n->type == XML_TEXT_NODE); assert(parent_style); assert(parent); assert(inline_container); if (parent_style->white_space == CSS_WHITE_SPACE_NORMAL || parent_style->white_space == CSS_WHITE_SPACE_NOWRAP) { char *text = squash_whitespace(n->content); if (!text) return false; /* if the text is just a space, combine it with the preceding * text node, if any */ if (text[0] == ' ' && text[1] == 0) { if (*inline_container) { if ((*inline_container)->last == 0) { LOG(("empty inline_container %p", *inline_container)); while (parent->parent && parent->parent->parent) parent = parent->parent; box_dump(parent, 0); } assert((*inline_container)->last != 0); (*inline_container)->last->space = 1; } free(text); return true; } if (!*inline_container) { /* this is the first inline node: make a container */ *inline_container = box_create(0, 0, 0, 0, 0, content); if (!*inline_container) { free(text); return false; } (*inline_container)->type = BOX_INLINE_CONTAINER; box_add_child(parent, *inline_container); } box = box_create(parent_style, href, target, title, 0, content); if (!box) { free(text); return false; } box->type = BOX_TEXT; box->text = talloc_strdup(content, text); free(text); if (!box->text) return false; box->length = strlen(box->text); /* strip ending space char off */ if (box->length > 1 && box->text[box->length - 1] == ' ') { box->space = 1; box->length--; } if (parent_style->text_transform != CSS_TEXT_TRANSFORM_NONE) box_text_transform(box->text, box->length, parent_style->text_transform); if (parent_style->white_space == CSS_WHITE_SPACE_NOWRAP) { unsigned int i; for (i = 0; i != box->length && box->text[i] != ' '; ++i) ; /* no body */ if (i != box->length) { /* there is a space in text block and we * want all spaces to be converted to NBSP */ /*box->text = cnv_space2nbsp(text); if (!box->text) { free(text); goto no_memory; } box->length = strlen(box->text);*/ } } box_add_child(*inline_container, box); if (box->text[0] == ' ') { box->length--; memmove(box->text, &box->text[1], box->length); if (box->prev != NULL) box->prev->space = 1; } } else { /* white-space: pre */ char *text = cnv_space2nbsp(n->content); char *current; /* note: pre-wrap/pre-line are unimplemented */ assert(parent_style->white_space == CSS_WHITE_SPACE_PRE || parent_style->white_space == CSS_WHITE_SPACE_PRE_LINE || parent_style->white_space == CSS_WHITE_SPACE_PRE_WRAP); if (!text) return false; if (parent_style->text_transform != CSS_TEXT_TRANSFORM_NONE) box_text_transform(text, strlen(text), parent_style->text_transform); current = text; /* swallow a single leading new line */ if (parent->strip_leading_newline) { switch (*current) { case '\n': current++; break; case '\r': current++; if (*current == '\n') current++; break; } parent->strip_leading_newline = 0; } do { size_t len = strcspn(current, "\r\n"); char old = current[len]; current[len] = 0; if (!*inline_container) { *inline_container = box_create(0, 0, 0, 0, 0, content); if (!*inline_container) { free(text); return false; } (*inline_container)->type = BOX_INLINE_CONTAINER; box_add_child(parent, *inline_container); } box = box_create(parent_style, href, target, title, 0, content); if (!box) { free(text); return false; } box->type = BOX_TEXT; box->text = talloc_strdup(content, current); if (!box->text) { free(text); return false; } box->length = strlen(box->text); box_add_child(*inline_container, box); current[len] = old; current += len; if (current[0] == '\r' && current[1] == '\n') { current += 2; *inline_container = 0; } else if (current[0] != 0) { current++; *inline_container = 0; } } while (*current); free(text); } return true; } /** * Get the style for an element. * * \param c content of type CONTENT_HTML that is being processed * \param parent_style style at this point in xml tree * \param n node in xml tree * \return the new style, or 0 on memory exhaustion * * The style is collected from three sources: * 1. any styles for this element in the document stylesheet(s) * 2. non-CSS HTML attributes * 3. the 'style' attribute */ struct css_style * box_get_style(struct content *c, struct css_style *parent_style, xmlNode *n) { char *s; struct css_style *style; struct css_style *style_new; char *url; url_func_result res; style = talloc_memdup(c, parent_style, sizeof *style); if (!style) return 0; style_new = talloc_memdup(c, &css_blank_style, sizeof *style_new); if (!style_new) return 0; css_get_style(c->data.html.working_stylesheet, n, style_new); css_cascade(style, style_new); /* style_new isn't needed past this point */ talloc_free(style_new); /* This property only applies to the body element, if you believe * the spec. Many browsers seem to allow it on other elements too, * so let's be generic ;) */ if (((s = (char *) xmlGetProp(n, (const xmlChar *) "background"))) && (style->background_image.type == CSS_BACKGROUND_IMAGE_NONE)) { res = url_join(s, c->data.html.base_url, &url); xmlFree(s); if (res == URL_FUNC_NOMEM) { return 0; } else if (res == URL_FUNC_OK) { /* if url is equivalent to the parent's url, * we've got infinite inclusion: ignore */ if (strcmp(url, c->data.html.base_url) == 0) free(url); else { style->background_image.type = CSS_BACKGROUND_IMAGE_URI; style->background_image.uri = talloc_strdup( c, url); free(url); if (!style->background_image.uri) return 0; } } } if (((s = (char *) xmlGetProp(n, (const xmlChar *) "bgcolor"))) && (style->background_color == TRANSPARENT)) { unsigned int r, g, b; if (s[0] == '#' && sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) style->background_color = (b << 16) | (g << 8) | r; else if (s[0] != '#') style->background_color = named_colour(s); xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "color"))) { unsigned int r, g, b; if (s[0] == '#' && sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) style->color = (b << 16) | (g << 8) | r; else if (s[0] != '#') style->color = named_colour(s); xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "height"))) { float value = isdigit(s[0]) ? atof(s) : -1; if (value <= 0 || strlen(s) == 0) { /* ignore negative values and height="" */ } else if (strrchr(s, '%')) { /* the specification doesn't make clear what * percentage heights mean, so ignore them */ } else { style->height.height = CSS_HEIGHT_LENGTH; style->height.length.unit = CSS_UNIT_PX; style->height.length.value = value; } xmlFree(s); } if (strcmp((const char *) n->name, "input") == 0) { if ((s = (char *) xmlGetProp(n, (const xmlChar *) "size"))) { int size = isdigit(s[0]) ? atoi(s): -1; if (0 < size) { char *type = (char *) xmlGetProp(n, (const xmlChar *) "type"); style->width.width = CSS_WIDTH_LENGTH; if (!type || strcasecmp(type, "text") == 0 || strcasecmp(type, "password") == 0) /* in characters for text, password */ style->width.value.length.unit = CSS_UNIT_EX; else if (strcasecmp(type, "file") != 0) /* in pixels otherwise; ignore width * on file, because we do them * differently to most browsers */ style->width.value.length.unit = CSS_UNIT_PX; style->width.value.length.value = size; if (type) xmlFree(type); } xmlFree(s); } } if (strcmp((const char *) n->name, "body") == 0) { if ((s = (char *) xmlGetProp(n, (const xmlChar *) "text"))) { unsigned int r, g, b; if (s[0] == '#' && sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) style->color = (b << 16) | (g << 8) | r; else if (s[0] != '#') style->color = named_colour(s); xmlFree(s); } } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "width"))) { float value = isdigit(s[0]) ? atof(s) : -1; if (value < 0 || strlen(s) == 0) { /* ignore negative values and width="" */ } else if (strrchr(s, '%')) { style->width.width = CSS_WIDTH_PERCENT; style->width.value.percent = value; } else { style->width.width = CSS_WIDTH_LENGTH; style->width.value.length.unit = CSS_UNIT_PX; style->width.value.length.value = value; } xmlFree(s); } if (strcmp((const char *) n->name, "textarea") == 0) { if ((s = (char *) xmlGetProp(n, (const xmlChar *) "rows"))) { int value = isdigit(s[0]) ? atoi(s): -1; if (0 < value) { style->height.height = CSS_HEIGHT_LENGTH; style->height.length.unit = CSS_UNIT_EM; style->height.length.value = value; } xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "cols"))) { int value = isdigit(s[0]) ? atoi(s): -1; if (0 < value) { style->width.width = CSS_WIDTH_LENGTH; style->width.value.length.unit = CSS_UNIT_EX; style->width.value.length.value = value; } xmlFree(s); } } if (strcmp((const char *) n->name, "table") == 0) { if ((s = (char *) xmlGetProp(n, (const xmlChar *) "cellspacing"))) { if (!strrchr(s, '%')) { /* % not implemented */ int value = isdigit(s[0]) ? atoi(s): -1; if (0 <= value) { style->border_spacing.border_spacing = CSS_BORDER_SPACING_LENGTH; style->border_spacing.horz.unit = style->border_spacing.vert.unit = CSS_UNIT_PX; style->border_spacing.horz.value = style->border_spacing.vert.value = value; } } xmlFree(s); } } if ((strcmp((const char *) n->name, "img") == 0) || (strcmp((const char *) n->name, "image") == 0) || (strcmp((const char *) n->name, "applet") == 0)) { if ((s = (char *) xmlGetProp(n, (const xmlChar *) "hspace"))) { if (!strrchr(s, '%')) { /* % not implemented */ int value = isdigit(s[0]) ? atoi(s): -1; if (0 <= value) { style->margin[LEFT].margin = CSS_MARGIN_LENGTH; style->margin[LEFT].value.length.value = value; style->margin[LEFT].value.length.unit = CSS_UNIT_PX; style->margin[RIGHT].margin = CSS_MARGIN_LENGTH; style->margin[RIGHT].value.length.value = value; style->margin[RIGHT].value.length.unit = CSS_UNIT_PX; } } xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "vspace"))) { if (!strrchr(s, '%')) { /* % not implemented */ int value = isdigit(s[0]) ? atoi(s): -1; if (0 <= value) { style->margin[TOP].margin = CSS_MARGIN_LENGTH; style->margin[TOP].value.length.value = value; style->margin[TOP].value.length.unit = CSS_UNIT_PX; style->margin[BOTTOM].margin = CSS_MARGIN_LENGTH; style->margin[BOTTOM].value.length.value = value; style->margin[BOTTOM].value.length.unit = CSS_UNIT_PX; } } xmlFree(s); } } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "style"))) { struct css_style *astyle; astyle = css_duplicate_style(&css_empty_style); if (!astyle) { xmlFree(s); return 0; } css_parse_property_list(c, astyle, s); css_cascade(style, astyle); css_free_style(astyle); xmlFree(s); } box_solve_display(style, !n->parent); return style; } /** * Calculate 'display' based on 'display', 'position', and 'float', as given * by CSS 2.1 9.7. * * \param style style to update * \param root this is the root element */ void box_solve_display(struct css_style *style, bool root) { if (style->display == CSS_DISPLAY_NONE) /* 1. */ return; else if (style->position == CSS_POSITION_ABSOLUTE || style->position == CSS_POSITION_FIXED) /* 2. */ style->float_ = CSS_FLOAT_NONE; else if (style->float_ != CSS_FLOAT_NONE) /* 3. */ ; else if (root) /* 4. */ ; else /* 5. */ return; /* Special case for absolute positioning: make absolute inlines into * inline block so that the boxes are constructed in an inline container * as if they were not absolutely positioned. Layout expects and * handles this. */ if ((style->position == CSS_POSITION_ABSOLUTE || style->position == CSS_POSITION_FIXED) && (style->display == CSS_DISPLAY_INLINE || style->display == CSS_DISPLAY_INLINE_BLOCK || style->display == CSS_DISPLAY_INLINE_TABLE)) { style->display = CSS_DISPLAY_INLINE_BLOCK; return; } /* map specified value to computed value using table given in 9.7 */ if (style->display == CSS_DISPLAY_INLINE_TABLE) style->display = CSS_DISPLAY_TABLE; else if (style->display == CSS_DISPLAY_LIST_ITEM || style->display == CSS_DISPLAY_TABLE) ; /* same as specified */ else style->display = CSS_DISPLAY_BLOCK; } /** * Set the cellpadding on a table. * * \param box box to set cellpadding on * \param value padding in pixels * * The descendants of the box are searched for table cells, and the padding is * set on each one. */ void box_set_cellpadding(struct box *box, int value) { /* The tree is not normalized yet, so accept cells not in rows and * rows not in row groups. */ struct box *child; for (child = box->children; child; child = child->next) { switch (child->type) { case BOX_TABLE_ROW_GROUP: case BOX_TABLE_ROW: box_set_cellpadding(child, value); break; case BOX_TABLE_CELL: for (unsigned int i = 0; i != 4; i++) { child->style->padding[i].padding = CSS_PADDING_LENGTH; child->style->padding[i].value.length.value = value; child->style->padding[i].value.length.unit = CSS_UNIT_PX; } break; default: break; } } } /** * Set the borders on a table. * * \param box box to set cellpadding on * \param value border in pixels * * The descendants of the box are searched for table cells, and the border is * set on each one. */ void box_set_table_border(struct box *box, int value, colour color) { struct box *child; if (box->type == BOX_TABLE) { for (unsigned int i = 0; i != 4; i++) { if (box->style->border[i].style == CSS_BORDER_STYLE_NONE) { box->style->border[i].color = color; box->style->border[i].width.width = CSS_BORDER_WIDTH_LENGTH; box->style->border[i].width.value.value = value; box->style->border[i].width.value.unit = CSS_UNIT_PX; box->style->border[i].style = CSS_BORDER_STYLE_OUTSET; } } } /* The tree is not normalized yet, so accept cells not in rows and * rows not in row groups. */ for (child = box->children; child; child = child->next) { switch (child->type) { case BOX_TABLE_ROW_GROUP: case BOX_TABLE_ROW: box_set_table_border(child, value, color); break; case BOX_TABLE_CELL: for (unsigned int i = 0; i != 4; i++) { if (child->style->border[i].style == CSS_BORDER_STYLE_NONE) { child->style->border[i].color = color; child->style->border[i].width.width = CSS_BORDER_WIDTH_LENGTH; child->style->border[i].width.value.value = 1; child->style->border[i].width.value.unit = CSS_UNIT_PX; child->style->border[i].style = CSS_BORDER_STYLE_INSET; } } break; default: break; } } } /** * Apply the CSS text-transform property to given text for its ASCII chars. * * \param s string to transform * \param len length of s * \param tt transform type */ void box_text_transform(char *s, unsigned int len, css_text_transform tt) { unsigned int i; if (len == 0) return; switch (tt) { case CSS_TEXT_TRANSFORM_UPPERCASE: for (i = 0; i < len; ++i) if (s[i] < 0x80) s[i] = toupper(s[i]); break; case CSS_TEXT_TRANSFORM_LOWERCASE: for (i = 0; i < len; ++i) if (s[i] < 0x80) s[i] = tolower(s[i]); break; case CSS_TEXT_TRANSFORM_CAPITALIZE: if (s[0] < 0x80) s[0] = toupper(s[0]); for (i = 1; i < len; ++i) if (s[i] < 0x80 && isspace(s[i - 1])) s[i] = toupper(s[i]); break; default: break; } } /** * \name Special case element handlers * * These functions are called by box_construct_element() when an element is * being converted, according to the entries in element_table. * * The parameters are the xmlNode, the content for the document, and a partly * filled in box structure for the element. * * Return true on success, false on memory exhaustion. Set *convert_children * to false if children of this element in the XML tree should be skipped (for * example, if they have been processed in some special way already). * * Elements ordered as in the HTML 4.01 specification. Section numbers in * brackets [] refer to the spec. * * \{ */ /** * Document body [7.5.1]. */ bool box_body(BOX_SPECIAL_PARAMS) { content->data.html.background_colour = box->style->background_color; return true; } /** * Forced line break [9.3.2]. */ bool box_br(BOX_SPECIAL_PARAMS) { box->type = BOX_BR; return true; } /** * Preformatted text [9.3.4]. */ bool box_pre(BOX_SPECIAL_PARAMS) { box->strip_leading_newline = 1; return true; } /** * Anchor [12.2]. */ bool box_a(BOX_SPECIAL_PARAMS) { bool ok; char *url; xmlChar *s; if ((s = xmlGetProp(n, (const xmlChar *) "href"))) { ok = box_extract_link((const char *) s, content->data.html.base_url, &url); xmlFree(s); if (!ok) return false; if (url) { box->href = talloc_strdup(content, url); free(url); if (!box->href) return false; } } /* name and id share the same namespace */ if (!box_get_attribute(n, "name", content, &box->id)) return false; /* target frame [16.3] */ if ((s = xmlGetProp(n, (const xmlChar *) "target"))) { if (!strcasecmp(s, "_blank")) box->target = TARGET_BLANK; else if (!strcasecmp(s, "_top")) box->target = TARGET_TOP; else if (!strcasecmp(s, "_parent")) box->target = TARGET_PARENT; else if (!strcasecmp(s, "_self")) /* the default may have been overridden by a * , so this is different to 0 */ box->target = TARGET_SELF; else if (('a' <= s[0] && s[0] <= 'z') || ('A' <= s[0] && s[0] <= 'Z')) { /* [6.16] */ box->target = talloc_strdup(content, s); if (!box->target) { xmlFree(s); return false; } } xmlFree(s); } return true; } /** * Embedded image [13.2]. */ bool box_image(BOX_SPECIAL_PARAMS) { bool ok; char *s, *url; xmlChar *alt, *src; if (box->style && box->style->display == CSS_DISPLAY_NONE) return true; /* handle alt text */ if ((alt = xmlGetProp(n, (const xmlChar *) "alt"))) { s = squash_whitespace(alt); xmlFree(alt); if (!s) return false; box->text = talloc_strdup(content, s); free(s); if (!box->text) return false; box->length = strlen(box->text); } /* imagemap associated with this image */ if (!box_get_attribute(n, "usemap", content, &box->usemap)) return false; if (box->usemap && box->usemap[0] == '#') box->usemap++; /* get image URL */ if (!(src = xmlGetProp(n, (const xmlChar *) "src"))) return true; if (!box_extract_link((char *) src, content->data.html.base_url, &url)) return false; xmlFree(src); if (!url) return true; /* start fetch */ ok = html_fetch_object(content, url, box, image_types, content->available_width, 1000, false); free(url); return ok; } /** * Generic embedded object [13.3]. */ bool box_object(BOX_SPECIAL_PARAMS) { struct object_params *params; struct object_param *param; xmlChar *codebase, *classid, *data; xmlNode *c; struct box *inline_container = 0; if (box->style && box->style->display == CSS_DISPLAY_NONE) return true; if (!box_get_attribute(n, "usemap", content, &box->usemap)) return false; if (box->usemap && box->usemap[0] == '#') box->usemap++; params = talloc(content, struct object_params); if (!params) return false; params->data = 0; params->type = 0; params->codetype = 0; params->codebase = 0; params->classid = 0; params->params = 0; /* codebase, classid, and data are URLs * (codebase is the base for the other two) */ if ((codebase = xmlGetProp(n, (const xmlChar *) "codebase"))) { if (!box_extract_link((char *) codebase, content->data.html.base_url, ¶ms->codebase)) return false; xmlFree(codebase); } if (!params->codebase) params->codebase = content->data.html.base_url; if ((classid = xmlGetProp(n, (const xmlChar *) "classid"))) { if (!box_extract_link((char *) classid, params->codebase, ¶ms->classid)) return false; xmlFree(classid); } if ((data = xmlGetProp(n, (const xmlChar *) "data"))) { if (!box_extract_link((char *) data, params->codebase, ¶ms->data)) return false; xmlFree(data); } if (!params->classid && !params->data) /* nothing to embed; ignore */ return true; /* Don't include ourself */ if (params->classid && strcmp(content->data.html.base_url, params->classid) == 0) return true; if (params->data && strcmp(content->data.html.base_url, params->data) == 0) return true; /* codetype and type are MIME types */ if (!box_get_attribute(n, "codetype", params, ¶ms->codetype)) return false; if (!box_get_attribute(n, "type", params, ¶ms->type)) return false; /* classid && !data => classid is used (consult codetype) * (classid || !classid) && data => data is used (consult type) * !classid && !data => invalid; ignored */ if (params->classid && !params->data && params->codetype && content_lookup(params->codetype) == CONTENT_OTHER) /* can't handle this MIME type */ return true; if (params->data && params->type && content_lookup(params->type) == CONTENT_OTHER) /* can't handle this MIME type */ return true; /* add parameters to linked list */ for (c = n->children; c; c = c->next) { if (c->type != XML_ELEMENT_NODE) continue; if (strcmp((const char *) c->name, "param") != 0) /* The first non-param child is the start of the alt * html. Therefore, we should break out of this loop. */ break; param = talloc(params, struct object_param); if (!param) return false; param->name = 0; param->value = 0; param->type = 0; param->valuetype = 0; param->next = 0; if (!box_get_attribute(c, "name", param, ¶m->name)) return false; if (!box_get_attribute(c, "value", param, ¶m->value)) return false; if (!box_get_attribute(c, "type", param, ¶m->type)) return false; if (!box_get_attribute(c, "valuetype", param, ¶m->valuetype)) return false; if (!param->valuetype) { param->valuetype = talloc_strdup(param, "data"); if (!param->valuetype) return false; } param->next = params->params; params->params = param; } box->object_params = params; /* start fetch (MIME type is ok or not specified) */ if (!html_fetch_object(content, params->data ? params->data : params->classid, box, 0, content->available_width, 1000, false)) return false; /* convert children and place into fallback */ for (c = n->children; c; c = c->next) { if (!convert_xml_to_box(c, content, box->style, box, &inline_container, 0, 0, 0)) return false; } box->fallback = box->children; box->children = box->last = 0; *convert_children = false; return true; } #if 0 /** * "Java applet" [13.4]. * * \todo This needs reworking to be compliant to the spec * For now, we simply ignore all applet tags. */ struct box_result box_applet(xmlNode *n, struct box_status *status, struct css_style *style) { struct box *box; struct object_params *po; struct object_param *pp = NULL; char *s; xmlNode *c; po = calloc(1, sizeof(struct object_params)); if (!po) return (struct box_result) {0, false, true}; box = box_create(style, status->href, 0, status->id, status->content->data.html.box_pool); if (!box) { free(po); return (struct box_result) {0, false, true}; } /* archive */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "archive")) != NULL) { /** \todo tokenise this comma separated list */ LOG(("archive '%s'", s)); po->data = strdup(s); xmlFree(s); if (!po->data) goto no_memory; } /* code */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "code")) != NULL) { LOG(("applet '%s'", s)); po->classid = strdup(s); xmlFree(s); if (!po->classid) goto no_memory; } /* object codebase */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "codebase")) != NULL) { po->codebase = strdup(s); LOG(("codebase: %s", s)); xmlFree(s); if (!po->codebase) goto no_memory; } /* parameters * parameter data is stored in a singly linked list. * po->params points to the head of the list. * new parameters are added to the head of the list. */ for (c = n->children; c != 0; c = c->next) { if (c->type != XML_ELEMENT_NODE) continue; if (strcmp((const char *) c->name, "param") == 0) { pp = calloc(1, sizeof(struct object_param)); if (!pp) goto no_memory; if ((s = (char *) xmlGetProp(c, (const xmlChar *) "name")) != NULL) { pp->name = strdup(s); xmlFree(s); if (!pp->name) goto no_memory; } if ((s = (char *) xmlGetProp(c, (const xmlChar *) "value")) != NULL) { pp->value = strdup(s); xmlFree(s); if (!pp->value) goto no_memory; } if ((s = (char *) xmlGetProp(c, (const xmlChar *) "type")) != NULL) { pp->type = strdup(s); xmlFree(s); if (!pp->type) goto no_memory; } if ((s = (char *) xmlGetProp(c, (const xmlChar *) "valuetype")) != NULL) { pp->valuetype = strdup(s); xmlFree(s); if (!pp->valuetype) goto no_memory; } else { pp->valuetype = strdup("data"); if (!pp->valuetype) goto no_memory; } pp->next = po->params; po->params = pp; } else { /* The first non-param child is the start * of the alt html. Therefore, we should * break out of this loop. */ break; } } box->object_params = po; /* start fetch */ if (plugin_decode(status->content, box)) return (struct box_result) {box, false, false}; return (struct box_result) {box, true, false}; no_memory: if (pp && pp != po->params) { /* ran out of memory creating parameter struct */ free(pp->name); free(pp->value); free(pp->type); free(pp->valuetype); free(pp); } box_free_object_params(po); box_free_box(box); return (struct box_result) {0, false, true}; } #endif /** * Window subdivision [16.2.1]. */ bool box_frameset(BOX_SPECIAL_PARAMS) { bool ok; if (content->data.html.frameset) { LOG(("Error: multiple framesets in document.")); return false; } content->data.html.frameset = talloc_zero(content, struct content_html_frames); if (!content->data.html.frameset) return false; ok = box_create_frameset(content->data.html.frameset, n, content); if (ok) box->style->display = CSS_DISPLAY_NONE; if (convert_children) *convert_children = false; return ok; } bool box_create_frameset(struct content_html_frames *f, xmlNode *n, struct content *content) { unsigned int row, col, index, i; unsigned int rows = 1, cols = 1; char *s, *url; struct frame_dimension *row_height = 0, *col_width = 0; xmlNode *c; struct content_html_frames *frame; bool default_border = true; colour default_border_colour = 0x000000; /* parse rows and columns */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "rows"))) { row_height = box_parse_multi_lengths(s, &rows); xmlFree(s); if (!row_height) return false; } else { row_height = calloc(1, sizeof(struct frame_dimension)); if (!row_height) return false; row_height->value = 100; row_height->unit = FRAME_DIMENSION_PERCENT; } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "cols"))) { col_width = box_parse_multi_lengths(s, &cols); xmlFree(s); if (!col_width) return false; } else { col_width = calloc(1, sizeof(struct frame_dimension)); if (!col_width) return false; col_width->value = 100; col_width->unit = FRAME_DIMENSION_PERCENT; } /* common extension: border="0|1" to control all children */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "border"))) { if ((s[0] == '0') && (s[1] == '\0')) default_border = false; xmlFree(s); } /* common extension: frameborder="yes|no" to control all children */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "frameborder"))) { if (!strcasecmp(s, "no")) default_border = false; xmlFree(s); } /* common extension: bordercolor="#RRGGBB|" to control all children */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "bordercolor"))) { unsigned int r, g, b; if (s[0] == '#' && sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) default_border_colour = (b << 16) | (g << 8) | r; else if (s[0] != '#') default_border_colour = named_colour(s); xmlFree(s); } /* update frameset and create default children */ f->cols = cols; f->rows = rows; f->scrolling = SCROLLING_NO; f->children = talloc_array(content, struct content_html_frames, (rows * cols)); for (row = 0; row < rows; row++) { for (col = 0; col < cols; col++) { index = (row * cols) + col; frame = &f->children[index]; frame->cols = 0; frame->rows = 0; frame->width = col_width[col]; frame->height = row_height[row]; frame->margin_width = 0; frame->margin_height = 0; frame->name = NULL; frame->url = NULL; frame->no_resize = false; frame->scrolling = SCROLLING_AUTO; frame->border = default_border; frame->border_colour = default_border_colour; frame->children = NULL; } } free(col_width); free(row_height); /* create the frameset windows */ c = n->children; for (row = 0; c && row < rows; row++) { for (col = 0; c && col < cols; col++) { while (c && !(c->type == XML_ELEMENT_NODE && ( strcmp((const char *) c->name, "frame") == 0 || strcmp((const char *) c->name, "frameset") == 0 ))) c = c->next; if (!c) break; /* get current frame */ index = (row * cols) + col; frame = &f->children[index]; /* nest framesets */ if (strcmp((const char *) c->name, "frameset") == 0) { frame->border = 0; if (!box_create_frameset(frame, c, content)) return false; c = c->next; continue; } /* get frame URL (not required) */ url = NULL; if ((s = (char *) xmlGetProp(c, (const xmlChar *) "src"))) { box_extract_link(s, content->data.html.base_url, &url); xmlFree(s); } /* copy url */ if (url) { /* no self-references */ if (strcmp(content->data.html.base_url, url)) frame->url = talloc_strdup(content, url); free(url); url = NULL; } /* fill in specified values */ if ((s = (char *) xmlGetProp(c, (const xmlChar *) "name"))) { frame->name = talloc_strdup(content, s); xmlFree(s); } frame->no_resize = xmlHasProp(c, (const xmlChar *) "noresize"); if ((s = (char *) xmlGetProp(c, (const xmlChar *) "frameborder"))) { i = atoi(s); frame->border = (i != 0); xmlFree(s); } if ((s = (char *) xmlGetProp(c, (const xmlChar *) "scrolling"))) { if (!strcasecmp(s, "yes")) frame->scrolling = SCROLLING_YES; else if (!strcasecmp(s, "no")) frame->scrolling = SCROLLING_NO; xmlFree(s); } if ((s = (char *) xmlGetProp(c, (const xmlChar *) "marginwidth"))) { frame->margin_width = atoi(s); xmlFree(s); } if ((s = (char *) xmlGetProp(c, (const xmlChar *) "marginheight"))) { frame->margin_height = atoi(s); xmlFree(s); } if ((s = (char *) xmlGetProp(c, (const xmlChar *) "bordercolor"))) { unsigned int r, g, b; if (s[0] == '#' && sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) frame->border_colour = (b << 16) | (g << 8) | r; else if (s[0] != '#') frame->border_colour = named_colour(s); xmlFree(s); } /* advance */ c = c->next; } } return true; } /** * Inline subwindow [16.5]. */ bool box_iframe(BOX_SPECIAL_PARAMS) { char *url, *s; struct content_html_iframe *iframe; int i; /* get frame URL */ if (!(s = (char *) xmlGetProp(n, (const xmlChar *) "src"))) return true; if (!box_extract_link(s, content->data.html.base_url, &url)) { xmlFree(s); return false; } xmlFree(s); if (!url) return true; /* don't include ourself */ if (strcmp(content->data.html.base_url, url) == 0) { free(url); return true; } /* create a new iframe */ iframe = talloc(content, struct content_html_iframe); if (!iframe) { free(url); return false; } iframe->box = box; iframe->margin_width = 0; iframe->margin_height = 0; iframe->name = NULL; iframe->url = talloc_strdup(content, url); iframe->scrolling = SCROLLING_AUTO; iframe->border = true; iframe->next = content->data.html.iframe; content->data.html.iframe = iframe; /* fill in specified values */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "name"))) { iframe->name = talloc_strdup(content, s); xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "frameborder"))) { i = atoi(s); iframe->border = (i != 0); xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "bordercolor"))) { unsigned int r, g, b; if (s[0] == '#' && sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) iframe->border_colour = (b << 16) | (g << 8) | r; else if (s[0] != '#') iframe->border_colour = named_colour(s); xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "scrolling"))) { if (!strcasecmp(s, "yes")) iframe->scrolling = SCROLLING_YES; else if (!strcasecmp(s, "no")) iframe->scrolling = SCROLLING_NO; xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "marginwidth"))) { iframe->margin_width = atoi(s); xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "marginheight"))) { iframe->margin_height = atoi(s); xmlFree(s); } /* release temporary memory */ free(url); /* box */ box->type = BOX_INLINE_BLOCK; assert(box->style); if (convert_children) *convert_children = false; return true; } /** * Interactive form [17.3]. */ bool box_form(BOX_SPECIAL_PARAMS) { char *xmlaction, *action, *faction, *method, *enctype, *charset, *target; form_method fmethod; struct form *form; url_func_result result; if (!(xmlaction = (char *) xmlGetProp(n, (const xmlChar *) "action"))) { /* the action attribute is required, but many forms fail to * specify it. In the case where it is _not_ specified, * follow other browsers and make the form action the * URI of the page the form is contained in. */ action = strdup(""); } else { action = strdup(xmlaction); xmlFree(xmlaction); } if (!action) return false; result = url_join(action, content->data.html.base_url, &faction); if (result != URL_FUNC_OK) { free(action); return false; } /* No longer needed */ free(action); fmethod = method_GET; if ((method = (char *) xmlGetProp(n, (const xmlChar *) "method"))) { if (strcasecmp(method, "post") == 0) { fmethod = method_POST_URLENC; if ((enctype = (char *) xmlGetProp(n, (const xmlChar *) "enctype"))) { if (strcasecmp(enctype, "multipart/form-data") == 0) fmethod = method_POST_MULTIPART; xmlFree(enctype); } } xmlFree(method); } /* acceptable encoding(s) for form data */ charset = (char *) xmlGetProp(n, (const xmlChar *) "accept-charset"); /* target for form data */ target = (char *) xmlGetProp(n, (const xmlChar *) "target"); form = form_new(faction, target, fmethod, charset, content->data.html.encoding); if (!form) { free(faction); xmlFree(target); xmlFree(charset); return false; } form->prev = content->data.html.forms; content->data.html.forms = form; return true; } /** * Form control [17.4]. */ bool box_input(BOX_SPECIAL_PARAMS) { struct form_control *gadget = NULL; char *s, *type, *url; url_func_result res; type = (char *) xmlGetProp(n, (const xmlChar *) "type"); if (type && strcasecmp(type, "password") == 0) { if (!box_input_text(n, content, box, 0, true)) goto no_memory; gadget = box->gadget; gadget->box = box; } else if (type && strcasecmp(type, "file") == 0) { box->type = BOX_INLINE_BLOCK; box->gadget = gadget = form_new_control(GADGET_FILE); if (!gadget) goto no_memory; gadget->box = box; } else if (type && strcasecmp(type, "hidden") == 0) { /* no box for hidden inputs */ box->style->display = CSS_DISPLAY_NONE; gadget = form_new_control(GADGET_HIDDEN); if (!gadget) goto no_memory; if ((s = (char *) xmlGetProp(n, (const xmlChar *) "value"))) { gadget->value = strdup(s); xmlFree(s); if (!gadget->value) goto no_memory; gadget->length = strlen(gadget->value); } } else if (type && (strcasecmp(type, "checkbox") == 0 || strcasecmp(type, "radio") == 0)) { box->gadget = gadget = form_new_control(type[0] == 'c' || type[0] == 'C' ? GADGET_CHECKBOX : GADGET_RADIO); if (!gadget) goto no_memory; gadget->box = box; gadget->selected = xmlHasProp(n, (const xmlChar *) "checked"); if ((s = (char *) xmlGetProp(n, (const xmlChar *) "value"))) { gadget->value = strdup(s); xmlFree(s); if (!gadget->value) goto no_memory; gadget->length = strlen(gadget->value); } } else if (type && (strcasecmp(type, "submit") == 0 || strcasecmp(type, "reset") == 0)) { struct box *inline_container, *inline_box; if (!box_button(n, content, box, 0)) goto no_memory; inline_container = box_create(0, 0, 0, 0, 0, content); if (!inline_container) goto no_memory; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(box->style, 0, 0, box->title, 0, content); if (!inline_box) goto no_memory; inline_box->type = BOX_TEXT; if (box->gadget->value != NULL) inline_box->text = talloc_strdup(content, box->gadget->value); else if (box->gadget->type == GADGET_SUBMIT) inline_box->text = talloc_strdup(content, messages_get("Form_Submit")); else inline_box->text = talloc_strdup(content, messages_get("Form_Reset")); if (!inline_box->text) goto no_memory; inline_box->length = strlen(inline_box->text); box_add_child(inline_container, inline_box); box_add_child(box, inline_container); } else if (type && strcasecmp(type, "button") == 0) { struct box *inline_container, *inline_box; if (!box_button(n, content, box, 0)) goto no_memory; inline_container = box_create(0, 0, 0, 0, 0, content); if (!inline_container) goto no_memory; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(box->style, 0, 0, box->title, 0, content); if (!inline_box) goto no_memory; inline_box->type = BOX_TEXT; if ((s = (char *) xmlGetProp(n, (const xmlChar *) "value"))) inline_box->text = talloc_strdup(content, s); else inline_box->text = talloc_strdup(content, "Button"); if (!inline_box->text) goto no_memory; inline_box->length = strlen(inline_box->text); box_add_child(inline_container, inline_box); box_add_child(box, inline_container); } else if (type && strcasecmp(type, "image") == 0) { box->gadget = gadget = form_new_control(GADGET_IMAGE); if (!gadget) goto no_memory; gadget->box = box; gadget->type = GADGET_IMAGE; if (box->style && box->style->display != CSS_DISPLAY_NONE) { if ((s = (char *) xmlGetProp(n, (const xmlChar*) "src"))) { res = url_join(s, content->data.html.base_url, &url); xmlFree(s); /* if url is equivalent to the parent's url, * we've got infinite inclusion. stop it here * also bail if url_join failed. */ if (res == URL_FUNC_OK && strcasecmp(url, content->data. html.base_url) != 0) { if (!html_fetch_object(content, url, box, image_types, content->available_width, 1000, false)) { free(url); goto no_memory; } } free(url); } } } else { /* the default type is "text" */ if (!box_input_text(n, content, box, 0, false)) goto no_memory; gadget = box->gadget; gadget->box = box; } if (type) xmlFree(type); if (gadget) { if (content->data.html.forms) form_add_control(content->data.html.forms, gadget); s = (char *) xmlGetProp(n, (const xmlChar *) "name"); if (s) { gadget->name = strdup(s); xmlFree(s); if (!gadget->name) goto no_memory; } } *convert_children = false; return true; no_memory: if (type) xmlFree(type); if (gadget) form_free_control(gadget); return false; } /** * Helper function for box_input(). */ bool box_input_text(BOX_SPECIAL_PARAMS, bool password) { char *s; struct box *inline_container, *inline_box; box->type = BOX_INLINE_BLOCK; box->gadget = form_new_control((password) ? GADGET_PASSWORD : GADGET_TEXTBOX); if (!box->gadget) return 0; box->gadget->box = box; if ((s = (char *) xmlGetProp(n, (const xmlChar *) "maxlength"))) { box->gadget->maxlength = atoi(s); xmlFree(s); } s = (char *) xmlGetProp(n, (const xmlChar *) "value"); box->gadget->value = strdup((s != NULL) ? s : ""); box->gadget->initial_value = strdup((box->gadget->value != NULL) ? box->gadget->value : ""); if (s) xmlFree(s); if (box->gadget->value == NULL || box->gadget->initial_value == NULL) { box_free(box); return NULL; } box->gadget->length = strlen(box->gadget->value); inline_container = box_create(0, 0, 0, 0, 0, content); if (!inline_container) return 0; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(box->style, 0, 0, box->title, 0, content); if (!inline_box) return 0; inline_box->type = BOX_TEXT; if (password) { inline_box->length = strlen(box->gadget->value); inline_box->text = talloc_array(content, char, inline_box->length + 1); if (!inline_box->text) return 0; memset(inline_box->text, '*', inline_box->length); inline_box->text[inline_box->length] = '\0'; } else { /* replace spaces/TABs with hard spaces to prevent line * wrapping */ char *text = cnv_space2nbsp(box->gadget->value); if (!text) return 0; inline_box->text = talloc_strdup(content, text); free(text); if (!inline_box->text) return 0; inline_box->length = strlen(inline_box->text); } box_add_child(inline_container, inline_box); box_add_child(box, inline_container); return box; } /** * Push button [17.5]. */ bool box_button(BOX_SPECIAL_PARAMS) { xmlChar *s; char *type = (char *) xmlGetProp(n, (const xmlChar *) "type"); box->type = BOX_INLINE_BLOCK; if (!type || strcasecmp(type, "submit") == 0) { box->gadget = form_new_control(GADGET_SUBMIT); } else if (strcasecmp(type, "reset") == 0) { box->gadget = form_new_control(GADGET_RESET); } else { /* type="button" or unknown: just render the contents */ xmlFree(type); return true; } if (type) xmlFree(type); if (!box->gadget) return false; if (content->data.html.forms) form_add_control(content->data.html.forms, box->gadget); box->gadget->box = box; if ((s = xmlGetProp(n, (const xmlChar *) "name")) != NULL) { box->gadget->name = strdup((char *) s); xmlFree(s); if (!box->gadget->name) return false; } if ((s = xmlGetProp(n, (const xmlChar *) "value")) != NULL) { box->gadget->value = strdup((char *) s); xmlFree(s); if (!box->gadget->value) return false; } return true; } /** * Option selector [17.6]. */ bool box_select(BOX_SPECIAL_PARAMS) { struct box *inline_container; struct box *inline_box; struct form_control *gadget; char* s; xmlNode *c, *c2; gadget = form_new_control(GADGET_SELECT); if (!gadget) return false; gadget->data.select.multiple = false; if ((s = (char *) xmlGetProp(n, (const xmlChar *) "multiple"))) { gadget->data.select.multiple = true; xmlFree(s); } gadget->data.select.items = NULL; gadget->data.select.last_item = NULL; gadget->data.select.num_items = 0; gadget->data.select.num_selected = 0; for (c = n->children; c; c = c->next) { if (strcmp((const char *) c->name, "option") == 0) { if (!box_select_add_option(gadget, c)) goto no_memory; } else if (strcmp((const char *) c->name, "optgroup") == 0) { for (c2 = c->children; c2; c2 = c2->next) { if (strcmp((const char *) c2->name, "option") == 0) { if (!box_select_add_option(gadget, c2)) goto no_memory; } } } } if (gadget->data.select.num_items == 0) { /* no options: ignore entire select */ form_free_control(gadget); return true; } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "name"))) { gadget->name = strdup(s); xmlFree(s); if (!gadget->name) goto no_memory; } box->type = BOX_INLINE_BLOCK; box->gadget = gadget; gadget->box = box; inline_container = box_create(0, 0, 0, 0, 0, content); if (!inline_container) goto no_memory; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(box->style, 0, 0, box->title, 0, content); if (!inline_box) goto no_memory; inline_box->type = BOX_TEXT; box_add_child(inline_container, inline_box); box_add_child(box, inline_container); if (!gadget->data.select.multiple && gadget->data.select.num_selected == 0) { gadget->data.select.current = gadget->data.select.items; gadget->data.select.current->initial_selected = gadget->data.select.current->selected = true; gadget->data.select.num_selected = 1; } if (gadget->data.select.num_selected == 0) inline_box->text = talloc_strdup(content, messages_get("Form_None")); else if (gadget->data.select.num_selected == 1) inline_box->text = talloc_strdup(content, gadget->data.select.current->text); else inline_box->text = talloc_strdup(content, messages_get("Form_Many")); if (!inline_box->text) goto no_memory; inline_box->length = strlen(inline_box->text); if (content->data.html.forms) form_add_control(content->data.html.forms, box->gadget); *convert_children = false; return true; no_memory: form_free_control(gadget); return false; } /** * Add an option to a form select control (helper function for box_select()). * * \param control select containing the option * \param n xml element node for