/* * Copyright 2005 James Bursa * Copyright 2003 Phil Mellor * Copyright 2005 John M Bell * Copyright 2006 Richard Wilson * Copyright 2008 Michael Drake * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** \file * Conversion of XML tree to box tree (implementation). */ #include #include #include #include #include #include #include #include #include #include "utils/config.h" #include "content/content_protected.h" #include "css/css.h" #include "css/utils.h" #include "css/select.h" #include "desktop/browser.h" #include "desktop/options.h" #include "render/box.h" #include "render/form.h" #include "render/html.h" #include "desktop/gui.h" #include "utils/locale.h" #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 #if defined(WITH_MNG) || defined(WITH_PNG) CONTENT_PNG, #endif #ifdef WITH_MNG CONTENT_JNG, CONTENT_MNG, #endif #if defined(WITH_NS_SVG) || defined(WITH_RSVG) CONTENT_SVG, #endif #if defined(WITH_SPRITE) || defined(WITH_NSSPRITE) CONTENT_SPRITE, #endif #ifdef WITH_DRAW CONTENT_DRAW, #endif #ifdef WITH_ARTWORKS CONTENT_ARTWORKS, #endif #ifdef WITH_WEBP CONTENT_WEBP, #endif #ifdef WITH_AMIGA_ICON CONTENT_AMIGA_ICON, #endif CONTENT_UNKNOWN }; /* 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, const css_computed_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, const css_computed_style *parent_style, struct box *parent, struct box **inline_container, char *href, const char *target, char *title); void box_construct_after(xmlNode *n, struct content *content, struct box *box, const css_computed_style *after_style); bool box_construct_text(xmlNode *n, struct content *content, const css_computed_style *parent_style, struct box *parent, struct box **inline_container, char *href, const char *target, char *title); static css_select_results * box_get_style(struct content *c, const css_computed_style *parent_style, xmlNode *n); static void box_text_transform(char *s, unsigned int len, enum css_text_transform_e 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_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}, {"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 = NULL; 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.object_count = 0; c->data.html.object = 0; /* The root box's style */ if (!convert_xml_to_box(n, c, NULL, &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 libcss' css_display enum */ 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_NONE, /*CSS_DISPLAY_TABLE_COLUMN_GROUP,*/ BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN,*/ BOX_TABLE_CELL, /*CSS_DISPLAY_TABLE_CELL,*/ BOX_INLINE, /*CSS_DISPLAY_TABLE_CAPTION,*/ BOX_NONE /*CSS_DISPLAY_NONE*/ }; /** * 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, or NULL for root box * \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, const css_computed_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, or NULL for root node * \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, const css_computed_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; css_select_results *styles = NULL; struct element_entry *element; xmlChar *title0; xmlNode *c; lwc_string *bgimage_uri; assert(n); assert(n->type == XML_ELEMENT_NODE); 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; styles = box_get_style(content, parent_style, n); if (!styles) return false; /* extract title attribute, if present */ if ((title0 = xmlGetProp(n, (const xmlChar *) "title"))) { char *title1 = squash_whitespace((char *) 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(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false, href, target, title, id, content); if (!box) return false; /* set box type from computed display */ if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE || css_computed_position(box->style) == CSS_POSITION_FIXED) && (css_computed_display_static(box->style) == CSS_DISPLAY_INLINE || css_computed_display_static(box->style) == CSS_DISPLAY_INLINE_BLOCK || css_computed_display_static(box->style) == CSS_DISPLAY_INLINE_TABLE)) { /* 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. */ box->type = box_map[CSS_DISPLAY_INLINE_BLOCK]; } else { /* Normal mapping */ box->type = box_map[css_computed_display(box->style, n->parent == NULL)]; } /* 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 (box->type == BOX_NONE || css_computed_display(box->style, n->parent == NULL) == CSS_DISPLAY_NONE) { /* Free style and invalidate box's style pointer */ css_select_results_destroy(styles); box->styles = NULL; box->style = NULL; /* If this box has an associated gadget, invalidate the * gadget's box pointer and our pointer to the gadget. */ if (box->gadget) { box->gadget->box = NULL; box->gadget = NULL; } /* 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 || css_computed_float(box->style) == CSS_FLOAT_LEFT || css_computed_float(box->style) == CSS_FLOAT_RIGHT)) { /* this is the first inline in a block: make a container */ *inline_container = box_create(NULL, 0, false, 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, box->style, parent, inline_container, href, target, title)) return false; inline_end = box_create(NULL, box->style, false, 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, box->style, box, &inline_container_c, href, target, title)) return false; } else { /* list item: compute marker, then treat as non-inline box */ if (css_computed_display(box->style, n->parent == NULL) == CSS_DISPLAY_LIST_ITEM) { lwc_string *image_uri; struct box *marker; marker = box_create(NULL, box->style, false, 0, 0, title, 0, content); if (!marker) return false; marker->type = BOX_BLOCK; /** \todo marker content (list-style-type) */ switch (css_computed_list_style_type(box->style)) { case CSS_LIST_STYLE_TYPE_DISC: /* 2022 BULLET */ marker->text = (char *) "\342\200\242"; marker->length = 3; break; case CSS_LIST_STYLE_TYPE_CIRCLE: /* 25CB WHITE CIRCLE */ marker->text = (char *) "\342\227\213"; marker->length = 3; break; case CSS_LIST_STYLE_TYPE_SQUARE: /* 25AA BLACK SMALL SQUARE */ marker->text = (char *) "\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: default: if (parent->last) { struct box *last = parent->last; /* Drill down into last child of parent * to find the list marker (if any) * * Floated list boxes end up as: * * parent * BOX_INLINE_CONTAINER * BOX_FLOAT_{LEFT,RIGHT} * BOX_BLOCK <-- list box * ... */ while (last != NULL) { if (last->list_marker != NULL) break; last = last->last; } if (last && last->list_marker) { marker->rows = 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 (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI && image_uri != NULL) { if (!html_fetch_object(content, lwc_string_data(image_uri), marker, image_types, content->available_width, 1000, false)) return false; } box->list_marker = marker; marker->parent = box; } /* float: insert a float box between the parent and * current node. Note: new parent will be the float */ if (css_computed_float(box->style) == CSS_FLOAT_LEFT || css_computed_float(box->style) == CSS_FLOAT_RIGHT) { parent = box_create(NULL, 0, false, href, target, title, 0, content); if (!parent) return false; if (css_computed_float(box->style) == CSS_FLOAT_LEFT) parent->type = BOX_FLOAT_LEFT; else parent->type = BOX_FLOAT_RIGHT; box_add_child(*inline_container, parent); } /* 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, box->style, box, &inline_container_c, href, target, title)) return false; if (css_computed_float(box->style) == 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); } xmlFree(s); } if ((s = (char *) xmlGetProp(n, (const xmlChar *) "rowspan"))) { if (isdigit(s[0])) { box->rows = strtol(s, NULL, 10); } xmlFree(s); } /* fetch any background image for this box */ if (css_computed_background_image(box->style, &bgimage_uri) == CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL) { if (!html_fetch_object(content, lwc_string_data(bgimage_uri), box, image_types, content->available_width, 1000, true)) return false; } /* handle the :after pseudo element */ /* TODO: Replace with true implementation. * Currently we only implement enough of this to support the * 'clearfix' hack, which is in widespread use and the layout * of many sites depend on. As such, only bother if box is a * block for now. */ if (box->type == BOX_BLOCK) { box_construct_after(n, content, box, box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]); } return true; } /** * Construct the box required for an :after pseudo element. * * \param n XML node of type XML_ELEMENT_NODE * \param content content of type CONTENT_HTML that is being processed * \param box box which may have an :after s * \param after_style complete computed style for after pseudo element * * TODO: * This is currently incomplete. It just does enough to support the clearfix * hack. ( http://www.positioniseverything.net/easyclearing.html ) * * To determine if an element has a pseudo element, we select for it and test to * see if the returned style's content property is set to normal. * * We don't actually support generated content yet. */ void box_construct_after(xmlNode *n, struct content *content, struct box *box, const css_computed_style *after_style) { struct box *after = 0; const css_computed_content_item *c_item; if (after_style == NULL || css_computed_content(after_style, &c_item) == CSS_CONTENT_NORMAL) { /* No pseudo element */ return; } /* create box for this element */ if (css_computed_display(after_style, n->parent == NULL) == CSS_DISPLAY_BLOCK) { /* currently only support block level after elements */ /** \todo Not wise to drop const from the computed style */ after = box_create(NULL, (css_computed_style *)after_style, false, NULL, NULL, NULL, NULL, content); if (after == NULL) { return; } /* set box type from computed display */ if ((css_computed_position(after_style) == CSS_POSITION_ABSOLUTE || css_computed_position(after_style) == CSS_POSITION_FIXED) && (css_computed_display_static(after_style) == CSS_DISPLAY_INLINE || css_computed_display_static(after_style) == CSS_DISPLAY_INLINE_BLOCK || css_computed_display_static(after_style) == CSS_DISPLAY_INLINE_TABLE)) { /* 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. */ after->type = box_map[CSS_DISPLAY_INLINE_BLOCK]; } else { /* Normal mapping */ after->type = box_map[css_computed_display( after_style, n->parent == NULL)]; } box_add_child(box, after); } } /** * 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, const css_computed_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 (css_computed_white_space(parent_style) == CSS_WHITE_SPACE_NORMAL || css_computed_white_space(parent_style) == CSS_WHITE_SPACE_NOWRAP) { char *text = squash_whitespace((char *) 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(stderr, 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(NULL, 0, false, 0, 0, 0, 0, content); if (!*inline_container) { free(text); return false; } (*inline_container)->type = BOX_INLINE_CONTAINER; box_add_child(parent, *inline_container); } /** \todo Dropping const here is not clever */ box = box_create(NULL, (css_computed_style *) parent_style, false, 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 (css_computed_text_transform(parent_style) != CSS_TEXT_TRANSFORM_NONE) box_text_transform(box->text, box->length, css_computed_text_transform(parent_style)); if (css_computed_white_space(parent_style) == 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((char *) n->content); char *current; enum css_white_space_e white_space = css_computed_white_space(parent_style); /* note: pre-wrap/pre-line are unimplemented */ assert(white_space == CSS_WHITE_SPACE_PRE || white_space == CSS_WHITE_SPACE_PRE_LINE || white_space == CSS_WHITE_SPACE_PRE_WRAP); if (!text) return false; if (css_computed_text_transform(parent_style) != CSS_TEXT_TRANSFORM_NONE) box_text_transform(text, strlen(text), css_computed_text_transform(parent_style)); 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(NULL, 0, false, 0, 0, 0, 0, content); if (!*inline_container) { free(text); return false; } (*inline_container)->type = BOX_INLINE_CONTAINER; box_add_child(parent, *inline_container); } /** \todo Dropping const isn't clever */ box = box_create(NULL, (css_computed_style *) parent_style, false, 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, or NULL for root * \param n node in xml tree * \return the new style, or NULL on memory exhaustion */ css_select_results *box_get_style(struct content *c, const css_computed_style *parent_style, xmlNode *n) { char *s; int pseudo_element; css_error error; css_stylesheet *inline_style = NULL; css_select_results *styles; /* Firstly, construct inline stylesheet, if any */ if ((s = (char *) xmlGetProp(n, (const xmlChar *) "style"))) { inline_style = nscss_create_inline_style( (uint8_t *) s, strlen(s), c->data.html.encoding, content__get_url(c), c->data.html.quirks != BINDING_QUIRKS_MODE_NONE, box_style_alloc, NULL); xmlFree(s); if (inline_style == NULL) return NULL; } /* Select partial style for element */ styles = nscss_get_style(c, n, CSS_MEDIA_SCREEN, inline_style, box_style_alloc, NULL); /* No longer need inline style */ if (inline_style != NULL) css_stylesheet_destroy(inline_style); /* Failed selecting partial style -- bail out */ if (styles == NULL) return NULL; /* If there's a parent style, compose with partial to obtain * complete computed style for element */ if (parent_style != NULL) { /* Complete the computed style, by composing with the parent * element's style */ error = css_computed_style_compose(parent_style, styles->styles[CSS_PSEUDO_ELEMENT_NONE], nscss_compute_font_size, NULL, styles->styles[CSS_PSEUDO_ELEMENT_NONE]); if (error != CSS_OK) { css_select_results_destroy(styles); return NULL; } } for (pseudo_element = CSS_PSEUDO_ELEMENT_NONE + 1; pseudo_element < CSS_PSEUDO_ELEMENT_COUNT; pseudo_element++) { if (pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LETTER || pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LINE) /* TODO: Handle first-line and first-letter pseudo * element computed style completion */ continue; if (styles->styles[pseudo_element] == NULL) /* There were no rules concerning this pseudo element */ continue; /* Complete the pseudo element's computed style, by composing * with the base element's style */ error = css_computed_style_compose( styles->styles[CSS_PSEUDO_ELEMENT_NONE], styles->styles[pseudo_element], nscss_compute_font_size, NULL, styles->styles[pseudo_element]); if (error != CSS_OK) { /* TODO: perhaps this shouldn't be quite so * catastrophic? */ css_select_results_destroy(styles); return NULL; } } return styles; } /** * 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, enum css_text_transform_e tt) { unsigned int i; if (len == 0) return; switch (tt) { case CSS_TEXT_TRANSFORM_UPPERCASE: for (i = 0; i < len; ++i) if ((unsigned char) s[i] < 0x80) s[i] = ls_toupper(s[i]); break; case CSS_TEXT_TRANSFORM_LOWERCASE: for (i = 0; i < len; ++i) if ((unsigned char) s[i] < 0x80) s[i] = ls_tolower(s[i]); break; case CSS_TEXT_TRANSFORM_CAPITALIZE: if ((unsigned char) s[0] < 0x80) s[0] = ls_toupper(s[0]); for (i = 1; i < len; ++i) if ((unsigned char) s[i] < 0x80 && ls_isspace(s[i - 1])) s[i] = ls_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) { css_color color; css_computed_background_color(box->style, &color); if (nscss_color_is_transparent(color)) content->data.html.background_colour = NS_TRANSPARENT; else content->data.html.background_colour = nscss_color_to_ns(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((const char *) s, "_blank")) box->target = TARGET_BLANK; else if (!strcasecmp((const char *) s, "_top")) box->target = TARGET_TOP; else if (!strcasecmp((const char *) s, "_parent")) box->target = TARGET_PARENT; else if (!strcasecmp((const char *) s, "_self")) /* the default may have been overridden by a * , so this is different to 0 */ box->target = TARGET_SELF; else { /* 6.16 says that frame names must begin with [a-zA-Z] * This doesn't match reality, so just take anything */ box->target = talloc_strdup(content, (const char *) 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 && css_computed_display(box->style, n->parent == NULL) == CSS_DISPLAY_NONE) return true; /* handle alt text */ if ((alt = xmlGetProp(n, (const xmlChar *) "alt"))) { s = squash_whitespace((const char *) 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 && css_computed_display(box->style, n->parent == NULL) == 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, false, 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.")); /* Don't convert children */ if (convert_children) *convert_children = false; /* And ignore this spurious frameset */ box->type = BOX_NONE; return true; } 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->type = BOX_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"))) { css_color color; if (nscss_parse_colour((const char *) s, &color)) default_border_colour = nscss_color_to_ns(color); 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") != NULL; 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"))) { css_color color; if (nscss_parse_colour((const char *) s, &color)) frame->border_colour = nscss_color_to_ns(color); 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; if (box->style && css_computed_display(box->style, n->parent == NULL) == CSS_DISPLAY_NONE) return true; if (box->style && css_computed_visibility(box->style) == CSS_VISIBILITY_HIDDEN) /* Don't create iframe discriptors for invisible iframes * TODO: handle hidden iframes at browser_window generation * time instead? */ return true; /* 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; /* Add this iframe to the linked list of iframes */ 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"))) { css_color color; if (nscss_parse_colour(s, &color)) iframe->border_colour = nscss_color_to_ns(color); 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); /* Showing iframe, so don't show alternate content */ if (convert_children) *convert_children = false; 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"); gadget = binding_get_control_for_node(content->data.html.parser_binding, n); if (!gadget) goto no_memory; box->gadget = gadget; gadget->box = box; if (type && strcasecmp(type, "password") == 0) { if (!box_input_text(n, content, box, 0, true)) goto no_memory; } else if (type && strcasecmp(type, "file") == 0) { box->type = BOX_INLINE_BLOCK; } else if (type && strcasecmp(type, "hidden") == 0) { /* no box for hidden inputs */ box->type = BOX_NONE; } else if (type && (strcasecmp(type, "checkbox") == 0 || strcasecmp(type, "radio") == 0)) { } else if (type && (strcasecmp(type, "submit") == 0 || strcasecmp(type, "reset") == 0 || strcasecmp(type, "button") == 0)) { struct box *inline_container, *inline_box; if (!box_button(n, content, box, 0)) goto no_memory; inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content); if (!inline_container) goto no_memory; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(NULL, box->style, false, 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 if (box->gadget->type == GADGET_RESET) inline_box->text = talloc_strdup(content, messages_get("Form_Reset")); 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) { gadget->type = GADGET_IMAGE; if (box->style && css_computed_display(box->style, n->parent == NULL) != 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; } if (type) xmlFree(type); *convert_children = false; return true; no_memory: if (type) xmlFree(type); return false; } /** * Helper function for box_input(). */ bool box_input_text(BOX_SPECIAL_PARAMS, bool password) { struct box *inline_container, *inline_box; box->type = BOX_INLINE_BLOCK; inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content); if (!inline_container) return false; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, content); if (!inline_box) return false; 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 false; 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 false; inline_box->text = talloc_strdup(content, text); free(text); if (!inline_box->text) return false; inline_box->length = strlen(inline_box->text); } box_add_child(inline_container, inline_box); box_add_child(box, inline_container); return true; } /** * Push button [17.5]. */ bool box_button(BOX_SPECIAL_PARAMS) { struct form_control *gadget; gadget = binding_get_control_for_node(content->data.html.parser_binding, n); if (!gadget) return false; box->gadget = gadget; gadget->box = box; box->type = BOX_INLINE_BLOCK; /* Just render the contents */ return true; } /** * Option selector [17.6]. */ bool box_select(BOX_SPECIAL_PARAMS) { struct box *inline_container; struct box *inline_box; struct form_control *gadget; xmlNode *c, *c2; gadget = binding_get_control_for_node(content->data.html.parser_binding, n); if (!gadget) return false; 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 */ return true; } box->type = BOX_INLINE_BLOCK; box->gadget = gadget; gadget->box = box; inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content); if (!inline_container) goto no_memory; inline_container->type = BOX_INLINE_CONTAINER; inline_box = box_create(NULL, box->style, false, 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); *convert_children = false; return true; no_memory: 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