From 64d8f9d5edca090ff3102da72d724905dc1676fd Mon Sep 17 00:00:00 2001 From: James Bursa Date: Sat, 26 Mar 2005 01:12:27 +0000 Subject: [project @ 2005-03-26 01:12:27 by bursa] Split box.c into box_construct.c, box_normalise.c, and box.c. svn path=/import/netsurf/; revision=1583 --- render/box_construct.c | 2384 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2384 insertions(+) create mode 100644 render/box_construct.c (limited to 'render/box_construct.c') diff --git a/render/box_construct.c b/render/box_construct.c new file mode 100644 index 000000000..61f20cb83 --- /dev/null +++ b/render/box_construct.c @@ -0,0 +1,2384 @@ +/* + * This file is part of NetSurf, http://netsurf.sourceforge.net/ + * 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 + */ + +/** \file + * Conversion of XML tree to box tree (implementation). + */ + +#define _GNU_SOURCE /* for strndup */ +#include +#include +#include +#include +#include +#include +#include "libxml/HTMLparser.h" +#include "netsurf/utils/config.h" +#include "netsurf/content/content.h" +#include "netsurf/css/css.h" +#include "netsurf/desktop/options.h" +#include "netsurf/render/box.h" +#include "netsurf/render/form.h" +#include "netsurf/render/html.h" +#ifdef riscos +#include "netsurf/desktop/gui.h" +#endif +#define NDEBUG +#include "netsurf/utils/log.h" +#include "netsurf/utils/messages.h" +#include "netsurf/utils/pool.h" +#include "netsurf/utils/url.h" +#include "netsurf/utils/utils.h" + + +/** Status of box tree construction. */ +struct box_status { + struct content *content; + char *href; + char *title; + struct form *current_form; + char *id; +}; + +/** Return type for special case element functions. */ +struct box_result { + /** Box for element, if any, 0 otherwise. */ + struct box *box; + /** Children of this element should be converted. */ + bool convert_children; + /** Memory was exhausted when handling the element. */ + bool memory_error; +}; + +/** MultiLength, as defined by HTML 4.01. */ +struct box_multi_length { + enum { LENGTH_PX, LENGTH_PERCENT, LENGTH_RELATIVE } type; + float value; +}; + + +static const content_type image_types[] = { +#ifdef WITH_JPEG + CONTENT_JPEG, +#endif +#ifdef WITH_GIF + CONTENT_GIF, +#endif +#ifdef WITH_PNG + CONTENT_PNG, +#endif +#ifdef WITH_MNG + CONTENT_JNG, + CONTENT_MNG, +#endif +#ifdef WITH_SPRITE + CONTENT_SPRITE, +#endif +#ifdef WITH_DRAW + CONTENT_DRAW, +#endif + CONTENT_UNKNOWN }; + +#define MAX_SPAN (100) + + +static bool convert_xml_to_box(xmlNode *n, struct content *content, + struct css_style *parent_style, + struct box *parent, struct box **inline_container, + struct box_status status); +struct css_style * box_get_style(struct content *c, + struct css_style *parent_style, + xmlNode *n); +static void box_text_transform(char *s, unsigned int len, + css_text_transform tt); +static struct box_result box_a(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_body(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_br(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_image(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_form(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_textarea(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_select(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_input(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box *box_input_text(xmlNode *n, struct box_status *status, + struct css_style *style, bool password); +static struct box_result box_button(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_frameset(xmlNode *n, struct box_status *status, + struct css_style *style); +static bool box_select_add_option(struct form_control *control, xmlNode *n); +static struct box_result box_object(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_embed(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_applet(xmlNode *n, struct box_status *status, + struct css_style *style); +static struct box_result box_iframe(xmlNode *n, struct box_status *status, + struct css_style *style); +static bool plugin_decode(struct content* content, struct box* box); +static struct box_multi_length *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 */ + struct box_result (*convert)(xmlNode *n, struct box_status *status, + struct css_style *style); +}; +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}, + {"img", box_image}, + {"input", box_input}, + {"object", box_object}, + {"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_status status = {c, 0, 0, 0, 0}; + 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 = css_duplicate_style(&css_base_style); + if (!c->data.html.style) + return false; + c->data.html.style->font_size.value.length.value = + option_font_size * 0.1; + + 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, status)) + return false; + if (!box_normalise_block(&root, c->data.html.box_pool)) + 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 status status for forms etc. + * \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, + struct box_status status) +{ + struct box *box = 0; + struct box *inline_container_c; + struct css_style *style = 0; + xmlNode *c; + char *s; + xmlChar *title0, *id0; + char *title = 0, *id = 0; + bool convert_children = true; + char *href_in = status.href; + + assert(n); + assert(parent_style); + assert(parent); + assert(inline_container); + + if (n->type == XML_ELEMENT_NODE) { + struct element_entry *element; + + gui_multitask(); + + style = box_get_style(content, parent_style, n); + if (!style) + goto no_memory; + if (style->display == CSS_DISPLAY_NONE) { + css_free_style(style); + goto end; + } + /* floats are treated as blocks */ + if (style->float_ == CSS_FLOAT_LEFT || + style->float_ == CSS_FLOAT_RIGHT) + if (style->display == CSS_DISPLAY_INLINE) + style->display = CSS_DISPLAY_BLOCK; + + /* extract title attribute, if present */ + if ((title0 = xmlGetProp(n, (const xmlChar *) "title"))) { + status.title = title = squash_whitespace(title0); + xmlFree(title0); + if (!title) + goto no_memory; + } + + /* extract id attribute, if present */ + if ((id0 = xmlGetProp(n, (const xmlChar *) "id"))) { + status.id = id = squash_whitespace(id0); + xmlFree(id0); + if (!id) + goto no_memory; + } + + /* 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 */ + struct box_result res = + element->convert(n, &status, style); + box = res.box; + convert_children = res.convert_children; + if (res.memory_error) + goto no_memory; + if (!box) { + /* no box for this element */ + assert(!convert_children); + css_free_style(style); + goto end; + } + } else { + /* general element */ + box = box_create(style, status.href, title, id, + content->data.html.box_pool); + if (!box) + goto no_memory; + } + /* set box type from style if it has not been set already */ + if (box->type == BOX_INLINE) + box->type = box_map[style->display]; + + } else if (n->type == XML_TEXT_NODE) { + /* text node: added to inline container below */ + + } else { + /* not an element or text node: ignore it (eg. comment) */ + goto end; + } + + content->size += sizeof(struct box) + sizeof(struct css_style); + + if (n->type == XML_TEXT_NODE && + (parent_style->white_space == CSS_WHITE_SPACE_NORMAL || + parent_style->white_space == CSS_WHITE_SPACE_NOWRAP)) { + char *text = squash_whitespace(n->content); + if (!text) + goto no_memory; + + /* 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) { + assert((*inline_container)->last != 0); + (*inline_container)->last->space = 1; + } + free(text); + goto end; + } + + if (!*inline_container) { + /* this is the first inline node: make a container */ + *inline_container = box_create(0, 0, 0, 0, + content->data.html.box_pool); + if (!*inline_container) { + free(text); + goto no_memory; + } + (*inline_container)->type = BOX_INLINE_CONTAINER; + box_add_child(parent, *inline_container); + } + + box = box_create(parent_style, status.href, title, id, + content->data.html.box_pool); + if (!box) { + free(text); + goto no_memory; + } + box->text = text; + box->style_clone = 1; + box->length = strlen(text); + /* strip ending space char off */ + if (box->length > 1 && 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 && 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; + } + goto end; + + } else if (n->type == XML_TEXT_NODE) { + /* 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) + goto no_memory; + if (parent_style->text_transform != CSS_TEXT_TRANSFORM_NONE) + box_text_transform(text, strlen(text), + parent_style->text_transform); + current = text; + 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, + content->data.html.box_pool); + if (!*inline_container) { + free(text); + goto no_memory; + } + (*inline_container)->type = + BOX_INLINE_CONTAINER; + box_add_child(parent, *inline_container); + } + box = box_create(parent_style, status.href, title, + id, content->data.html.box_pool); + if (!box) { + free(text); + goto no_memory; + } + box->type = BOX_INLINE; + box->style_clone = 1; + box->text = strdup(current); + if (!box->text) { + free(text); + goto no_memory; + } + 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); + goto end; + + } else if (box->type == BOX_INLINE || + box->type == BOX_INLINE_BLOCK || + style->float_ == CSS_FLOAT_LEFT || + style->float_ == CSS_FLOAT_RIGHT || + box->type == BOX_BR) { + /* this is an inline box */ + if (!*inline_container) { + /* this is the first inline node: make a container */ + *inline_container = box_create(0, 0, 0, 0, + content->data.html.box_pool); + if (!*inline_container) + goto no_memory; + (*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) { + for (c = n->children; c != 0; c = c->next) + if (!convert_xml_to_box(c, content, + style, parent, + inline_container, + status)) + goto no_memory; + } + goto end; + } else if (box->type == BOX_INLINE_BLOCK) { + /* inline block box: add to tree and recurse */ + box_add_child(*inline_container, box); + if (convert_children) { + inline_container_c = 0; + for (c = n->children; c != 0; c = c->next) + if (!convert_xml_to_box(c, content, + style, box, + &inline_container_c, + status)) + goto no_memory; + } + goto end; + } else { + /* float: insert a float box between the parent and + * current node */ + assert(style->float_ == CSS_FLOAT_LEFT || + style->float_ == CSS_FLOAT_RIGHT); + parent = box_create(0, status.href, title, id, + content->data.html.box_pool); + if (!parent) + goto no_memory; + if (style->float_ == CSS_FLOAT_LEFT) + parent->type = BOX_FLOAT_LEFT; + else + parent->type = BOX_FLOAT_RIGHT; + box_add_child(*inline_container, parent); + if (box->type == BOX_INLINE || + box->type == BOX_INLINE_BLOCK) + box->type = BOX_BLOCK; + } + } + + assert(n->type == XML_ELEMENT_NODE); + + /* non-inline box: add to tree and recurse */ + box_add_child(parent, box); + if (convert_children) { + inline_container_c = 0; + for (c = n->children; c != 0; c = c->next) + if (!convert_xml_to_box(c, content, style, + box, &inline_container_c, status)) + goto no_memory; + } + if (style->float_ == CSS_FLOAT_NONE) + /* new inline container unless this is a float */ + *inline_container = 0; + + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "colspan")) != NULL) { + box->columns = strtol(s, NULL, 10); + if (MAX_SPAN < box->columns) + box->columns = 1; + xmlFree(s); + } + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "rowspan")) != NULL) { + box->rows = strtol(s, NULL, 10); + if (MAX_SPAN < box->rows) + box->rows = 1; + xmlFree(s); + } + +end: + free(title); + free(id); + if (!href_in) + xmlFree(status.href); + + /* Now fetch any background image for this box */ + if (box && box->style && box->style->background_image.type == + CSS_BACKGROUND_IMAGE_URI) { + char *url = strdup(box->style->background_image.uri); + if (!url) + return false; + /* start fetch */ + if (!html_fetch_object(content, url, box, image_types, + content->available_width, 1000, true)) + return false; + } + + return true; + +no_memory: + free(title); + free(id); + if (!href_in) + xmlFree(status.href); + if (style && !box) + css_free_style(style); + + return false; +} + + +/** + * 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; + unsigned int i; + unsigned int stylesheet_count = c->data.html.stylesheet_count; + struct content **stylesheet = c->data.html.stylesheet_content; + struct css_style *style; + struct css_style *style_new; + char *url; + url_func_result res; + + style = css_duplicate_style(parent_style); + if (!style) + return 0; + + style_new = css_duplicate_style(&css_blank_style); + if (!style_new) { + css_free_style(style); + return 0; + } + + for (i = 0; i != stylesheet_count; i++) { + if (stylesheet[i]) { + assert(stylesheet[i]->type == CONTENT_CSS); + css_get_style(stylesheet[i], n, style_new); + } + } + css_cascade(style, style_new); + + /* style_new isn't needed past this point */ + css_free_style(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"))) { + res = url_join(s, c->data.html.base_url, &url); + if (res == URL_FUNC_NOMEM) { + css_free_style(style); + 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 = url; + } + } + xmlFree(s); + } + + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "bgcolor")) != NULL) { + 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")) != NULL) { + 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")) != NULL) { + 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 { + 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")) != NULL) { + int size = atoi(s); + 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, file */ + style->width.value.length.unit = CSS_UNIT_EX; + else if (strcasecmp(type, "file") != 0) + /* in pixels otherwise */ + 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")) != NULL) { + 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")) != NULL) { + float value = atof(s); + 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")) != NULL) { + int value = atoi(s); + 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")) != NULL) { + int value = atoi(s); + 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 = atoi(s); + 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; + } + } + } + style->html_style.cellpadding.type = CSS_CELLPADDING_VALUE; + if ((s = (char *) xmlGetProp(n, + (const xmlChar *) "cellpadding"))) { + if (!strrchr(s, '%')) { /* % not implemented */ + int value = atoi(s); + if (0 <= value) { + style->html_style.cellpadding.value = value; + /* todo: match and rules and don't set if they are */ + for (i = 0; i < 4; i++) + style->padding[i].override_cellpadding = false; + } + } + } else { + style->html_style.cellpadding.value = 1; + } + } + + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "style")) != NULL) { + struct css_style *astyle; + astyle = css_duplicate_style(&css_empty_style); + if (!astyle) { + xmlFree(s); + css_free_style(style); + return 0; + } + css_parse_property_list(c, astyle, s); + css_cascade(style, astyle); + css_free_style(astyle); + xmlFree(s); + } + + return style; +} + + +/** + * 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; + } +} + + +/* + * Special case elements + * + * These functions are called by convert_xml_to_box when an element is being + * converted, according to the entries in element_table (top of file). + * + * The parameters are the xmlNode, a status structure for the conversion, and + * the style found for the element. + * + * If a box is created, it is returned in the result structure. The + * convert_children field should be 1 if convert_xml_to_box should convert the + * node's children recursively, 0 if it should ignore them (presumably they + * have been processed in some way by the function). If box is 0, no box will + * be created for that element, and convert_children must be 0. + */ +struct box_result box_a(xmlNode *n, struct box_status *status, + struct css_style *style) +{ + struct box *box; + char *s, *s1; + char *id = status->id; + + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "href")) != NULL) + status->href = s; + + /* name and id share the same namespace */ + if ((s1 = (char *) xmlGetProp(n, (const xmlChar *) "name")) != NULL) { + if (status->id && strcmp(status->id, s1) == 0) { + /* both specified and they match => ok */ + id = status->id; + } + else if (!status->id) { + /* only name specified */ + id = squash_whitespace(s1); + if (!id) { + xmlFree(s1); + return (struct box_result) {0, false, true}; + } + } else + /* both specified but no match */ + id = 0; + + xmlFree(s1); + } + + box = box_create(style, status->href, status->title, id, + status->content->data.html.box_pool); + + if (id && id != status->id) + free(id); + + if (!box) + return (struct box_result) {0, false, true}; + + return (struct box_result) {box, true, false}; +} + +struct box_result box_body(xmlNode *n, struct box_status *status, + struct css_style *style) +{ + struct box *box; + status->content->data.html.background_colour = style->background_color; + box = box_create(style, status->href, status->title, status->id, + status->content->data.html.box_pool); + if (!box) + return (struct box_result) {0, false, true}; + return (struct box_result) {box, true, false}; +} + +struct box_result box_br(xmlNode *n, struct box_status *status, + struct css_style *style) +{ + struct box *box; + box = box_create(style, status->href, status->title, status->id, + status->content->data.html.box_pool); + if (!box) + return (struct box_result) {0, false, true}; + box->type = BOX_BR; + return (struct box_result) {box, false, false}; +} + +struct box_result box_image(xmlNode *n, struct box_status *status, + struct css_style *style) +{ + struct box *box; + char *s, *url, *s1, *map; + xmlChar *s2; + url_func_result res; + + box = box_create(style, status->href, status->title, status->id, + status->content->data.html.box_pool); + if (!box) + return (struct box_result) {0, false, true}; + + /* handle alt text */ + if ((s2 = xmlGetProp(n, (const xmlChar *) "alt")) != NULL) { + box->text = squash_whitespace(s2); + xmlFree(s2); + if (!box->text) + return (struct box_result) {0, false, true}; + box->length = strlen(box->text); + } + + /* imagemap associated with this image */ + if ((map = xmlGetProp(n, (const xmlChar *) "usemap")) != NULL) { + if (map[0] == '#') + box->usemap = strdup(map + 1); + else + box->usemap = strdup(map); + xmlFree(map); + if (!box->usemap) { + free(box->text); + return (struct box_result) {0, false, true}; + } + } + + /* img without src is an error */ + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "src")) == NULL) + return (struct box_result) {box, false, false}; + + /* remove leading and trailing whitespace */ + s1 = strip(s); + res = url_join(s1, status->content->data.html.base_url, &url); + xmlFree(s); + if (res == URL_FUNC_NOMEM) { + free(box->text); + return (struct box_result) {0, false, true}; + } else if (res == URL_FUNC_FAILED) { + return (struct box_result) {box, false, false}; + } + + if (strcmp(url, status->content->data.html.base_url) == 0) + /* if url is equivalent to the parent's url, + * we've got infinite inclusion: ignore */ + return (struct box_result) {box, false, false}; + + /* start fetch */ + if (!html_fetch_object(status->content, url, box, image_types, + status->content->available_width, 1000, false)) + return (struct box_result) {0, false, true}; + + return (struct box_result) {box, false, false}; +} + +struct box_result box_form(xmlNode *n, struct box_status *status, + struct css_style *style) +{ + char *action, *method, *enctype; + form_method fmethod; + struct box *box; + struct form *form; + + box = box_create(style, status->href, status->title, status->id, + status->content->data.html.box_pool); + if (!box) + return (struct box_result) {0, false, true}; + + if (!(action = (char *) xmlGetProp(n, (const xmlChar *) "action"))) { + /* the action attribute is required */ + return (struct box_result) {box, true, false}; + } + + 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); + } + + status->current_form = form = form_new(action, fmethod); + if (!form) { + xmlFree(action); + return (struct box_result) {0, false, true}; + } + + return (struct box_result) {box, true, false}; +} + +struct box_result box_textarea(xmlNode *n, struct box_status *status, + struct css_style *style) +{ + /* A textarea is an INLINE_BLOCK containing a single INLINE_CONTAINER, + * which contains the text as runs of INLINE separated by BR. There is + * at least one INLINE. The first and last boxes are INLINE. + * Consecutive BR may not be present. These constraints are satisfied + * by using a 0-length INLINE for blank lines. */ + + xmlChar *content, *current; + struct box *box, *inline_container, *inline_box, *br_box; + char *s; + size_t len; + + box = box_create(style, NULL, 0, status->id, + status->content->data.html.box_pool); + if (!box) + return (struct box_result) {0, false, true}; + box->type = BOX_INLINE_BLOCK; + box->gadget = form_new_control(GADGET_TEXTAREA); + if (!box->gadget) + return (struct box_result) {0, false, true}; + box->gadget->box = box; + + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "name")) != NULL) { + box->gadget->name = strdup(s); + xmlFree(s); + if (!box->gadget->name) + return (struct box_result) {0, false, true}; + } + + inline_container = box_create(0, 0, 0, 0, + status->content->data.html.box_pool); + if (!inline_container) + return (struct box_result) {0, false, true}; + inline_container->type = BOX_INLINE_CONTAINER; + box_add_child(box, inline_container); + + current = content = xmlNodeGetContent(n); + while (1) { + /* BOX_INLINE */ + len = strcspn(current, "\r\n"); + s = strndup(current, len); + if (!s) { + box_free(box); + xmlFree(content); + return (struct box_result) {NULL, false, false}; + } + + inline_box = box_create(style, 0, 0, 0, + status->content->data.html.box_pool); + if (!inline_box) + return (struct box_result) {0, false, true}; + inline_box->type = BOX_INLINE; + inline_box->style_clone = 1; + inline_box->text = s; + inline_box->length = len; + box_add_child(inline_container, inline_box); + + current += len; + if (current[0] == 0) + /* finished */ + break; + + /* BOX_BR */ + br_box = box_create(style, 0, 0, 0, + status->content->data.html.box_pool); + if (!br_box) + return (struct box_result) {0, false, true}; + br_box->type = BOX_BR; + br_box->style_clone = 1; + box_add_child(inline_container, br_box); + + if (current[0] == '\r' && current[1] == '\n') + current += 2; + else + current++; + } + xmlFree(content); + + if (status->current_form) + form_add_control(status->current_form, box->gadget); + + return (struct box_result) {box, false, false}; +} + +struct box_result box_select(xmlNode *n, struct box_status *status, + struct css_style *style) +{ + struct box *box; + 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 (struct box_result) {0, false, true}; + + gadget->data.select.multiple = false; + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "multiple")) != NULL) { + 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 (struct box_result) {0, false, false}; + } + + if ((s = (char *) xmlGetProp(n, (const xmlChar *) "name")) != NULL) { + gadget->name = strdup(s); + xmlFree(s); + if (!gadget->name) + goto no_memory; + } + + box = box_create(style, NULL, 0, status->id, + status->content->data.html.box_pool); + if (!box) + goto no_memory; + box->type = BOX_INLINE_BLOCK; + box->gadget = gadget; + gadget->box = box; + + inline_container = box_create(0, 0, 0, 0, + status->content->data.html.box_pool); + if (!inline_container) + goto no_memory; + inline_container->type = BOX_INLINE_CONTAINER; + inline_box = box_create(style, 0, 0, 0, + status->content->data.html.box_pool); + if (!inline_box) + goto no_memory; + inline_box->type = BOX_INLINE; + inline_box->style_clone = 1; + 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 = strdup(messages_get("Form_None")); + else if (gadget->data.select.num_selected == 1) + inline_box->text = strdup(gadget->data.select.current->text); + else + inline_box->text = strdup(messages_get("Form_Many")); + if (!inline_box->text) + goto no_memory; + + inline_box->length = strlen(inline_box->text); + + if (status->current_form) + form_add_control(status->current_form, gadget); + + return (struct box_result) {box, false, false}; + +no_memory: + form_free_control(gadget); + return (struct box_result) {0, false, true}; +} + + +/** + * Add an option to a form select control. + * + * \param control select containing the option + * \param n xml element node for