/* * 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 2004 James Bursa * Copyright 2003 Phil Mellor * Copyright 2003 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/font.h" #include "netsurf/render/form.h" #include "netsurf/render/html.h" #ifdef riscos #include "netsurf/desktop/gui.h" #endif #ifdef WITH_PLUGIN #include "netsurf/riscos/plugin.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 ) struct span_info { unsigned int row_span; bool auto_row; bool auto_column; }; struct columns { unsigned int current_column; bool extra; /* Number of columns in main part of table 1..max columns */ unsigned int num_columns; /* Information about columns in main table, array 0 to num_columns - 1 */ struct span_info *spans; /* Number of columns that have cells after a colspan 0 */ unsigned int extra_columns; /* Number of rows in table */ unsigned int num_rows; }; 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 bool box_normalise_block(struct box *block, pool box_pool); static bool box_normalise_table(struct box *table, pool box_pool); static void box_normalise_table_spans( struct box *table ); static bool box_normalise_table_row_group(struct box *row_group, struct columns *col_info, pool box_pool); static bool box_normalise_table_row(struct box *row, struct columns *col_info, pool box_pool); static bool calculate_table_row(struct columns *col_info, unsigned int col_span, unsigned int row_span, unsigned int *start_column); static bool box_normalise_inline_container(struct box *cont, pool box_pool); static void box_free_box(struct box *box); 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, char* url, struct box* box, struct object_params* po); static struct box_multi_length *box_parse_multi_lengths(const char *s, unsigned int *count); static bool box_contains_point(struct box *box, int x, int y); #define box_is_float(box) (box->type == BOX_FLOAT_LEFT || \ box->type == BOX_FLOAT_RIGHT) /* 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])) /** * Add a child to a box tree node. */ void box_add_child(struct box * parent, struct box * child) { if (parent->children != 0) { /* has children already */ parent->last->next = child; child->prev = parent->last; } else { /* this is the first child */ parent->children = child; child->prev = 0; } parent->last = child; child->parent = parent; } /** * Create a box tree node. * * \param style style for the box (not copied) * \param href href for the box (copied), or 0 * \param title title for the box (copied), or 0 * \param id id for the box (copied), or 0 * \param box_pool pool to allocate box from * \return allocated and initialised box, or 0 on memory exhaustion */ struct box * box_create(struct css_style * style, const char *href, const char *title, const char *id, pool box_pool) { unsigned int i; struct box *box; char *href1 = 0; char *title1 = 0; char *id1 = 0; if (href) href1 = strdup(href); if (title) title1 = strdup(title); if (id) id1 = strdup(id); if ((href && !href1) || (title && !title1) || (id && !id1)) { free(href1); free(title1); free(id1); return 0; } box = pool_alloc(box_pool, sizeof (struct box)); if (!box) { free(href1); free(title1); free(id1); return 0; } box->type = BOX_INLINE; box->style = style; box->x = box->y = 0; box->width = UNKNOWN_WIDTH; box->height = 0; box->descendant_x0 = box->descendant_y0 = 0; box->descendant_x1 = box->descendant_y1 = 0; for (i = 0; i != 4; i++) box->margin[i] = box->padding[i] = box->border[i] = 0; box->scroll_x = box->scroll_y = 0; box->min_width = 0; box->max_width = UNKNOWN_MAX_WIDTH; box->text = NULL; box->length = 0; box->space = 0; box->clone = 0; box->style_clone = 0; box->href = href1; box->title = title1; box->columns = 1; box->rows = 1; box->start_column = 0; box->next = NULL; box->prev = NULL; box->children = NULL; box->last = NULL; box->parent = NULL; box->float_children = NULL; box->next_float = NULL; box->col = NULL; box->font = NULL; box->gadget = NULL; box->usemap = NULL; box->id = id1; box->background = NULL; box->object = NULL; box->object_params = NULL; return box; } /** * Insert a new box as a sibling to a box in a tree. */ void box_insert_sibling(struct box *box, struct box *new_box) { new_box->parent = box->parent; new_box->prev = box; new_box->next = box->next; box->next = new_box; if (new_box->next) new_box->next->prev = new_box; else if (new_box->parent) new_box->parent->last = new_box; } /** * 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.fonts = nsfont_new_set(); if (!c->data.html.fonts) { css_free_style(c->data.html.style); return false; } 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->font = nsfont_open(content->data.html.fonts, box->style); 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; assert(parent_style->white_space == CSS_WHITE_SPACE_PRE); 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->font = nsfont_open(content->data.html.fonts, box->style); 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; } } } } 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); box->font = nsfont_open(status->content->data.html.fonts, style); } /* 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; inline_box->font = nsfont_open(status->content->data.html.fonts, style); 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); inline_box->font = nsfont_open(status->content->data.html.fonts, style); 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