Subversion Repositories Kolibri OS

Rev

Rev 4364 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. /*
  2.  * Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org>
  3.  *
  4.  * This file is part of NetSurf, http://www.netsurf-browser.org/
  5.  *
  6.  * NetSurf is free software; you can redistribute it and/or modify
  7.  * it under the terms of the GNU General Public License as published by
  8.  * the Free Software Foundation; version 2 of the License.
  9.  *
  10.  * NetSurf is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  */
  18.  
  19. #include <assert.h>
  20. #include <stdbool.h>
  21. #include <string.h>
  22. #include <strings.h>
  23.  
  24. #include "content/content_protected.h"
  25. #include "content/urldb.h"
  26. #include "css/internal.h"
  27. #include "css/select.h"
  28. #include "css/utils.h"
  29. #include "desktop/gui.h"
  30. #include "desktop/options.h"
  31. #include "utils/corestrings.h"
  32. #include "utils/log.h"
  33. #include "utils/url.h"
  34. #include "utils/utils.h"
  35.  
  36. static css_error node_name(void *pw, void *node, css_qname *qname);
  37. static css_error node_classes(void *pw, void *node,
  38.                 lwc_string ***classes, uint32_t *n_classes);
  39. static css_error node_id(void *pw, void *node, lwc_string **id);
  40. static css_error named_ancestor_node(void *pw, void *node,
  41.                 const css_qname *qname, void **ancestor);
  42. static css_error named_parent_node(void *pw, void *node,
  43.                 const css_qname *qname, void **parent);
  44. static css_error named_sibling_node(void *pw, void *node,
  45.                 const css_qname *qname, void **sibling);
  46. static css_error named_generic_sibling_node(void *pw, void *node,
  47.                 const css_qname *qname, void **sibling);
  48. static css_error parent_node(void *pw, void *node, void **parent);
  49. static css_error sibling_node(void *pw, void *node, void **sibling);
  50. static css_error node_has_name(void *pw, void *node,
  51.                 const css_qname *qname, bool *match);
  52. static css_error node_has_class(void *pw, void *node,
  53.                 lwc_string *name, bool *match);
  54. static css_error node_has_id(void *pw, void *node,
  55.                 lwc_string *name, bool *match);
  56. static css_error node_has_attribute(void *pw, void *node,
  57.                 const css_qname *qname, bool *match);
  58. static css_error node_has_attribute_equal(void *pw, void *node,
  59.                 const css_qname *qname, lwc_string *value,
  60.                 bool *match);
  61. static css_error node_has_attribute_dashmatch(void *pw, void *node,
  62.                 const css_qname *qname, lwc_string *value,
  63.                 bool *match);
  64. static css_error node_has_attribute_includes(void *pw, void *node,
  65.                 const css_qname *qname, lwc_string *value,
  66.                 bool *match);
  67. static css_error node_has_attribute_prefix(void *pw, void *node,
  68.                 const css_qname *qname, lwc_string *value,
  69.                 bool *match);
  70. static css_error node_has_attribute_suffix(void *pw, void *node,
  71.                 const css_qname *qname, lwc_string *value,
  72.                 bool *match);
  73. static css_error node_has_attribute_substring(void *pw, void *node,
  74.                 const css_qname *qname, lwc_string *value,
  75.                 bool *match);
  76. static css_error node_is_root(void *pw, void *node, bool *match);
  77. static css_error node_count_siblings(void *pw, void *node,
  78.                 bool same_name, bool after, int32_t *count);
  79. static css_error node_is_empty(void *pw, void *node, bool *match);
  80. static css_error node_is_link(void *pw, void *node, bool *match);
  81. static css_error node_is_visited(void *pw, void *node, bool *match);
  82. static css_error node_is_hover(void *pw, void *node, bool *match);
  83. static css_error node_is_active(void *pw, void *node, bool *match);
  84. static css_error node_is_focus(void *pw, void *node, bool *match);
  85. static css_error node_is_enabled(void *pw, void *node, bool *match);
  86. static css_error node_is_disabled(void *pw, void *node, bool *match);
  87. static css_error node_is_checked(void *pw, void *node, bool *match);
  88. static css_error node_is_target(void *pw, void *node, bool *match);
  89. static css_error node_is_lang(void *pw, void *node,
  90.                 lwc_string *lang, bool *match);
  91. static css_error node_presentational_hint(void *pw, void *node,
  92.                 uint32_t property, css_hint *hint);
  93. static css_error ua_default_for_property(void *pw, uint32_t property,
  94.                 css_hint *hint);
  95.  
  96. static int cmp_colour_name(const void *a, const void *b);
  97. static bool parse_named_colour(const char *data, css_color *result);
  98. static bool parse_dimension(const char *data, bool strict,
  99.                 css_fixed *length, css_unit *unit);
  100. static bool parse_number(const char *data, bool non_negative, bool real,
  101.                 css_fixed *value, size_t *consumed);
  102. static bool parse_font_size(const char *size, uint8_t *val,
  103.                 css_fixed *len, css_unit *unit);
  104.  
  105. static css_computed_style *nscss_get_initial_style(nscss_select_ctx *ctx,
  106.                 css_allocator_fn, void *pw);
  107.  
  108. static bool isWhitespace(char c);
  109. static bool isHex(char c);
  110. static uint8_t charToHex(char c);
  111.  
  112. /**
  113.  * Selection callback table for libcss
  114.  */
  115. static css_select_handler selection_handler = {
  116.         CSS_SELECT_HANDLER_VERSION_1,
  117.  
  118.         node_name,
  119.         node_classes,
  120.         node_id,
  121.         named_ancestor_node,
  122.         named_parent_node,
  123.         named_sibling_node,
  124.         named_generic_sibling_node,
  125.         parent_node,
  126.         sibling_node,
  127.         node_has_name,
  128.         node_has_class,
  129.         node_has_id,
  130.         node_has_attribute,
  131.         node_has_attribute_equal,
  132.         node_has_attribute_dashmatch,
  133.         node_has_attribute_includes,
  134.         node_has_attribute_prefix,
  135.         node_has_attribute_suffix,
  136.         node_has_attribute_substring,
  137.         node_is_root,
  138.         node_count_siblings,
  139.         node_is_empty,
  140.         node_is_link,
  141.         node_is_visited,
  142.         node_is_hover,
  143.         node_is_active,
  144.         node_is_focus,
  145.         node_is_enabled,
  146.         node_is_disabled,
  147.         node_is_checked,
  148.         node_is_target,
  149.         node_is_lang,
  150.         node_presentational_hint,
  151.         ua_default_for_property,
  152.         nscss_compute_font_size
  153. };
  154.  
  155. /**
  156.  * Create an inline style
  157.  *
  158.  * \param data          Source data
  159.  * \param len           Length of data in bytes
  160.  * \param charset       Charset of data, or NULL if unknown
  161.  * \param url           URL of document containing data
  162.  * \param allow_quirks  True to permit CSS parsing quirks
  163.  * \param alloc         Memory allocation function
  164.  * \param pw            Private word for allocator
  165.  * \return Pointer to stylesheet, or NULL on failure.
  166.  */
  167. css_stylesheet *nscss_create_inline_style(const uint8_t *data, size_t len,
  168.                 const char *charset, const char *url, bool allow_quirks,
  169.                 css_allocator_fn alloc, void *pw)
  170. {
  171.         css_stylesheet_params params;
  172.         css_stylesheet *sheet;
  173.         css_error error;
  174.  
  175.         params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
  176.         params.level = CSS_LEVEL_DEFAULT;
  177.         params.charset = charset;
  178.         params.url = url;
  179.         params.title = NULL;
  180.         params.allow_quirks = allow_quirks;
  181.         params.inline_style = true;
  182.         params.resolve = nscss_resolve_url;
  183.         params.resolve_pw = NULL;
  184.         params.import = NULL;
  185.         params.import_pw = NULL;
  186.         params.color = gui_system_colour;
  187.         params.color_pw = NULL;
  188.         params.font = NULL;
  189.         params.font_pw = NULL;
  190.  
  191.         error = css_stylesheet_create(&params, alloc, pw, &sheet);
  192.         if (error != CSS_OK) {
  193.                 LOG(("Failed creating sheet: %d", error));
  194.                 return NULL;
  195.         }
  196.  
  197.         error = css_stylesheet_append_data(sheet, data, len);
  198.         if (error != CSS_OK && error != CSS_NEEDDATA) {
  199.                 LOG(("failed appending data: %d", error));
  200.                 css_stylesheet_destroy(sheet);
  201.                 return NULL;
  202.         }
  203.  
  204.         error = css_stylesheet_data_done(sheet);
  205.         if (error != CSS_OK) {
  206.                 LOG(("failed completing parse: %d", error));
  207.                 css_stylesheet_destroy(sheet);
  208.                 return NULL;
  209.         }
  210.  
  211.         return sheet;
  212. }
  213.  
  214. /**
  215.  * Get a style selection results (partial computed styles) for an element
  216.  *
  217.  * \param ctx             CSS selection context
  218.  * \param n               Element to select for
  219.  * \param media           Permitted media types
  220.  * \param inline_style    Inline style associated with element, or NULL
  221.  * \param alloc           Memory allocation function
  222.  * \param pw              Private word for allocator
  223.  * \return Pointer to selection results (containing partial computed styles),
  224.  *         or NULL on failure
  225.  */
  226. css_select_results *nscss_get_style(nscss_select_ctx *ctx, dom_node *n,
  227.                 uint64_t media, const css_stylesheet *inline_style,
  228.                 css_allocator_fn alloc, void *pw)
  229. {
  230.         css_select_results *styles;
  231.         css_error error;
  232.  
  233.         error = css_select_style(ctx->ctx, n, media, inline_style,
  234.                         &selection_handler, ctx, &styles);
  235.         if (error != CSS_OK) {
  236.                 return NULL;
  237.         }
  238.  
  239.         return styles;
  240. }
  241.  
  242. /**
  243.  * Get an initial style
  244.  *
  245.  * \param ctx    CSS selection context
  246.  * \param alloc  Memory allocation function
  247.  * \param pw     Private word for allocator
  248.  * \return Pointer to partial computed style, or NULL on failure
  249.  */
  250. css_computed_style *nscss_get_initial_style(nscss_select_ctx *ctx,
  251.                 css_allocator_fn alloc, void *pw)
  252. {
  253.         css_computed_style *style;
  254.         css_error error;
  255.  
  256.         error = css_computed_style_create(alloc, pw, &style);
  257.         if (error != CSS_OK)
  258.                 return NULL;
  259.  
  260.         error = css_computed_style_initialise(style, &selection_handler, ctx);
  261.         if (error != CSS_OK) {
  262.                 css_computed_style_destroy(style);
  263.                 return NULL;
  264.         }
  265.  
  266.         return style;
  267. }
  268.  
  269. /**
  270.  * Get a blank style
  271.  *
  272.  * \param ctx     CSS selection context
  273.  * \param parent  Parent style to cascade inherited properties from
  274.  * \param alloc   Memory allocation function
  275.  * \param pw      Private word for allocator
  276.  * \return Pointer to blank style, or NULL on failure
  277.  */
  278. css_computed_style *nscss_get_blank_style(nscss_select_ctx *ctx,
  279.                 const css_computed_style *parent,
  280.                 css_allocator_fn alloc, void *pw)
  281. {
  282.         css_computed_style *partial;
  283.         css_error error;
  284.  
  285.         partial = nscss_get_initial_style(ctx, alloc, pw);
  286.         if (partial == NULL)
  287.                 return NULL;
  288.  
  289.         error = css_computed_style_compose(parent, partial,
  290.                         nscss_compute_font_size, NULL, partial);
  291.         if (error != CSS_OK) {
  292.                 css_computed_style_destroy(partial);
  293.                 return NULL;
  294.         }
  295.  
  296.         return partial;
  297. }
  298.  
  299. /**
  300.  * Font size computation callback for libcss
  301.  *
  302.  * \param pw      Computation context
  303.  * \param parent  Parent font size (absolute)
  304.  * \param size    Font size to compute
  305.  * \return CSS_OK on success
  306.  *
  307.  * \post \a size will be an absolute font size
  308.  */
  309. css_error nscss_compute_font_size(void *pw, const css_hint *parent,
  310.                 css_hint *size)
  311. {
  312.         /**
  313.          * Table of font-size keyword scale factors
  314.          *
  315.          * These are multiplied by the configured default font size
  316.          * to produce an absolute size for the relevant keyword
  317.          */
  318.         static const css_fixed factors[] = {
  319.                 FLTTOFIX(0.5625), /* xx-small */
  320.                 FLTTOFIX(0.6250), /* x-small */
  321.                 FLTTOFIX(0.8125), /* small */
  322.                 FLTTOFIX(1.0000), /* medium */
  323.                 FLTTOFIX(1.1250), /* large */
  324.                 FLTTOFIX(1.5000), /* x-large */
  325.                 FLTTOFIX(2.0000)  /* xx-large */
  326.         };
  327.         css_hint_length parent_size;
  328.  
  329.         /* Grab parent size, defaulting to medium if none */
  330.         if (parent == NULL) {
  331.                 parent_size.value = FDIV(FMUL(factors[CSS_FONT_SIZE_MEDIUM - 1],
  332.                                               INTTOFIX(nsoption_int(font_size))),
  333.                                          INTTOFIX(10));
  334.                 parent_size.unit = CSS_UNIT_PT;
  335.         } else {
  336.                 assert(parent->status == CSS_FONT_SIZE_DIMENSION);
  337.                 assert(parent->data.length.unit != CSS_UNIT_EM);
  338.                 assert(parent->data.length.unit != CSS_UNIT_EX);
  339.                 assert(parent->data.length.unit != CSS_UNIT_PCT);
  340.  
  341.                 parent_size = parent->data.length;
  342.         }
  343.  
  344.         /* assert(size->status != CSS_FONT_SIZE_INHERIT); */
  345.  
  346.         if (size->status < CSS_FONT_SIZE_LARGER) {
  347.                 /* Keyword -- simple */
  348.                 size->data.length.value = FDIV(FMUL(factors[size->status - 1],
  349.                                                     INTTOFIX(nsoption_int(font_size))),
  350.                                                F_10);
  351.                 size->data.length.unit = CSS_UNIT_PT;
  352.         } else if (size->status == CSS_FONT_SIZE_LARGER) {
  353.                 /** \todo Step within table, if appropriate */
  354.                 size->data.length.value =
  355.                                 FMUL(parent_size.value, FLTTOFIX(1.2));
  356.                 size->data.length.unit = parent_size.unit;
  357.         } else if (size->status == CSS_FONT_SIZE_SMALLER) {
  358.                 /** \todo Step within table, if appropriate */
  359.                 size->data.length.value =
  360.                                 FDIV(parent_size.value, FLTTOFIX(1.2));
  361.                 size->data.length.unit = parent_size.unit;
  362.         } else if (size->data.length.unit == CSS_UNIT_EM ||
  363.                         size->data.length.unit == CSS_UNIT_EX) {
  364.                 size->data.length.value =
  365.                         FMUL(size->data.length.value, parent_size.value);
  366.  
  367.                 if (size->data.length.unit == CSS_UNIT_EX) {
  368.                         /* 1ex = 0.6em in NetSurf */
  369.                         size->data.length.value = FMUL(size->data.length.value,
  370.                                         FLTTOFIX(0.6));
  371.                 }
  372.  
  373.                 size->data.length.unit = parent_size.unit;
  374.         } else if (size->data.length.unit == CSS_UNIT_PCT) {
  375.                 size->data.length.value = FDIV(FMUL(size->data.length.value,
  376.                                 parent_size.value), INTTOFIX(100));
  377.                 size->data.length.unit = parent_size.unit;
  378.         }
  379.  
  380.         size->status = CSS_FONT_SIZE_DIMENSION;
  381.  
  382.         return CSS_OK;
  383. }
  384.  
  385. /**
  386.  * Parser for colours specified in attribute values.
  387.  *
  388.  * \param data    Data to parse (NUL-terminated)
  389.  * \param result  Pointer to location to receive resulting css_color
  390.  * \return true on success, false on invalid input
  391.  */
  392. bool nscss_parse_colour(const char *data, css_color *result)
  393. {
  394.         size_t len = strlen(data);
  395.         uint8_t r, g, b;
  396.  
  397.         /* 2 */
  398.         if (len == 0)
  399.                 return false;
  400.  
  401.         /* 3 */
  402.         if (len == SLEN("transparent") && strcasecmp(data, "transparent") == 0)
  403.                 return false;
  404.  
  405.         /* 4 */
  406.         if (parse_named_colour(data, result))
  407.                 return true;
  408.  
  409.         /** \todo Implement HTML5's utterly insane legacy colour parsing */
  410.  
  411.         if (data[0] == '#') {
  412.                 data++;
  413.                 len--;
  414.         }
  415.  
  416.         if (len == 3 && isHex(data[0]) && isHex(data[1]) && isHex(data[2])) {
  417.                 r = charToHex(data[0]);
  418.                 g = charToHex(data[1]);
  419.                 b = charToHex(data[2]);
  420.  
  421.                 r |= (r << 4);
  422.                 g |= (g << 4);
  423.                 b |= (b << 4);
  424.  
  425.                 *result = (0xff << 24) | (r << 16) | (g << 8) | b;
  426.  
  427.                 return true;
  428.         } else if (len == 6 && isHex(data[0]) && isHex(data[1]) &&
  429.                         isHex(data[2]) && isHex(data[3]) && isHex(data[4]) &&
  430.                         isHex(data[5])) {
  431.                 r = (charToHex(data[0]) << 4) | charToHex(data[1]);
  432.                 g = (charToHex(data[2]) << 4) | charToHex(data[3]);
  433.                 b = (charToHex(data[4]) << 4) | charToHex(data[5]);
  434.  
  435.                 *result = (0xff << 24) | (r << 16) | (g << 8) | b;
  436.  
  437.                 return true;
  438.         }
  439.  
  440.         return false;
  441. }
  442.  
  443. /******************************************************************************
  444.  * Style selection callbacks                                                  *
  445.  ******************************************************************************/
  446.  
  447. /**
  448.  * Callback to retrieve a node's name.
  449.  *
  450.  * \param pw     HTML document
  451.  * \param node   DOM node
  452.  * \param qname  Pointer to location to receive node name
  453.  * \return CSS_OK on success,
  454.  *         CSS_NOMEM on memory exhaustion.
  455.  */
  456. css_error node_name(void *pw, void *node, css_qname *qname)
  457. {
  458.         dom_node *n = node;
  459.         dom_string *name;
  460.         dom_exception err;
  461.  
  462.         err = dom_node_get_node_name(n, &name);
  463.         if (err != DOM_NO_ERR)
  464.                 return CSS_NOMEM;
  465.  
  466.         qname->ns = NULL;
  467.  
  468.         err = dom_string_intern(name, &qname->name);
  469.         if (err != DOM_NO_ERR) {
  470.                 dom_string_unref(name);
  471.                 return CSS_NOMEM;
  472.         }
  473.  
  474.         dom_string_unref(name);
  475.  
  476.         return CSS_OK;
  477. }
  478.  
  479. /**
  480.  * Callback to retrieve a node's classes.
  481.  *
  482.  * \param pw         HTML document
  483.  * \param node       DOM node
  484.  * \param classes    Pointer to location to receive class name array
  485.  * \param n_classes  Pointer to location to receive length of class name array
  486.  * \return CSS_OK on success,
  487.  *         CSS_NOMEM on memory exhaustion.
  488.  *
  489.  * \note The returned array will be destroyed by libcss. Therefore, it must
  490.  *       be allocated using the same allocator as used by libcss during style
  491.  *       selection.
  492.  */
  493. css_error node_classes(void *pw, void *node,
  494.                 lwc_string ***classes, uint32_t *n_classes)
  495. {
  496.         dom_node *n = node;
  497.         dom_exception err;
  498.  
  499.         *classes = NULL;
  500.         *n_classes = 0;
  501.  
  502.         err = dom_element_get_classes(n, classes, n_classes);
  503.         if (err != DOM_NO_ERR)
  504.                 return CSS_NOMEM;
  505.  
  506.         return CSS_OK;
  507. }
  508.  
  509. /**
  510.  * Callback to retrieve a node's ID.
  511.  *
  512.  * \param pw    HTML document
  513.  * \param node  DOM node
  514.  * \param id    Pointer to location to receive id value
  515.  * \return CSS_OK on success,
  516.  *         CSS_NOMEM on memory exhaustion.
  517.  */
  518. css_error node_id(void *pw, void *node, lwc_string **id)
  519. {
  520.         dom_node *n = node;
  521.         dom_string *attr;
  522.         dom_exception err;
  523.  
  524.         *id = NULL;
  525.  
  526.         /** \todo Assumes an HTML DOM */
  527.         err = dom_html_element_get_id(n, &attr);
  528.         if (err != DOM_NO_ERR)
  529.                 return CSS_NOMEM;
  530.  
  531.         if (attr != NULL) {
  532.                 err = dom_string_intern(attr, id);
  533.                 if (err != DOM_NO_ERR) {
  534.                         dom_string_unref(attr);
  535.                         return CSS_NOMEM;
  536.                 }
  537.                 dom_string_unref(attr);
  538.         }
  539.  
  540.         return CSS_OK;
  541. }
  542.  
  543. /**
  544.  * Callback to find a named ancestor node.
  545.  *
  546.  * \param pw        HTML document
  547.  * \param node      DOM node
  548.  * \param qname     Node name to search for
  549.  * \param ancestor  Pointer to location to receive ancestor
  550.  * \return CSS_OK.
  551.  *
  552.  * \post \a ancestor will contain the result, or NULL if there is no match
  553.  */
  554. css_error named_ancestor_node(void *pw, void *node,
  555.                 const css_qname *qname, void **ancestor)
  556. {
  557.         dom_element_named_ancestor_node(node, qname->name,
  558.                         (struct dom_element **)ancestor);
  559.  
  560.         return CSS_OK;
  561. }
  562.  
  563. /**
  564.  * Callback to find a named parent node
  565.  *
  566.  * \param pw      HTML document
  567.  * \param node    DOM node
  568.  * \param qname   Node name to search for
  569.  * \param parent  Pointer to location to receive parent
  570.  * \return CSS_OK.
  571.  *
  572.  * \post \a parent will contain the result, or NULL if there is no match
  573.  */
  574. css_error named_parent_node(void *pw, void *node,
  575.                 const css_qname *qname, void **parent)
  576. {
  577.         dom_element_named_parent_node(node, qname->name,
  578.                         (struct dom_element **)parent);
  579.  
  580.         return CSS_OK;
  581. }
  582.  
  583. /**
  584.  * Callback to find a named sibling node.
  585.  *
  586.  * \param pw       HTML document
  587.  * \param node     DOM node
  588.  * \param qname    Node name to search for
  589.  * \param sibling  Pointer to location to receive sibling
  590.  * \return CSS_OK.
  591.  *
  592.  * \post \a sibling will contain the result, or NULL if there is no match
  593.  */
  594. css_error named_sibling_node(void *pw, void *node,
  595.                 const css_qname *qname, void **sibling)
  596. {
  597.         dom_node *n = node;
  598.         dom_node *prev;
  599.         dom_exception err;
  600.  
  601.         *sibling = NULL;
  602.  
  603.         /* Find sibling element */
  604.         err = dom_node_get_previous_sibling(n, &n);
  605.         if (err != DOM_NO_ERR)
  606.                 return CSS_OK;
  607.  
  608.         while (n != NULL) {
  609.                 dom_node_type type;
  610.  
  611.                 err = dom_node_get_node_type(n, &type);
  612.                 if (err != DOM_NO_ERR) {
  613.                         dom_node_unref(n);
  614.                         return CSS_OK;
  615.                 }
  616.  
  617.                 if (type == DOM_ELEMENT_NODE)
  618.                         break;
  619.  
  620.                 err = dom_node_get_previous_sibling(n, &prev);
  621.                 if (err != DOM_NO_ERR) {
  622.                         dom_node_unref(n);
  623.                         return CSS_OK;
  624.                 }
  625.  
  626.                 dom_node_unref(n);
  627.                 n = prev;
  628.         }
  629.  
  630.         if (n != NULL) {
  631.                 dom_string *name;
  632.  
  633.                 err = dom_node_get_node_name(n, &name);
  634.                 if (err != DOM_NO_ERR) {
  635.                         dom_node_unref(n);
  636.                         return CSS_OK;
  637.                 }
  638.  
  639.                 dom_node_unref(n);
  640.  
  641.                 if (dom_string_caseless_lwc_isequal(name, qname->name)) {
  642.                         *sibling = n;
  643.                 }
  644.  
  645.                 dom_string_unref(name);
  646.         }
  647.  
  648.         return CSS_OK;
  649. }
  650.  
  651. /**
  652.  * Callback to find a named generic sibling node.
  653.  *
  654.  * \param pw       HTML document
  655.  * \param node     DOM node
  656.  * \param qname    Node name to search for
  657.  * \param sibling  Pointer to location to receive ancestor
  658.  * \return CSS_OK.
  659.  *
  660.  * \post \a sibling will contain the result, or NULL if there is no match
  661.  */
  662. css_error named_generic_sibling_node(void *pw, void *node,
  663.                 const css_qname *qname, void **sibling)
  664. {
  665.         dom_node *n = node;
  666.         dom_node *prev;
  667.         dom_exception err;
  668.  
  669.         *sibling = NULL;
  670.  
  671.         err = dom_node_get_previous_sibling(n, &n);
  672.         if (err != DOM_NO_ERR)
  673.                 return CSS_OK;
  674.  
  675.         while (n != NULL) {
  676.                 dom_node_type type;
  677.                 dom_string *name;
  678.  
  679.                 err = dom_node_get_node_type(n, &type);
  680.                 if (err != DOM_NO_ERR) {
  681.                         dom_node_unref(n);
  682.                         return CSS_OK;
  683.                 }
  684.  
  685.                 if (type == DOM_ELEMENT_NODE) {
  686.                         err = dom_node_get_node_name(n, &name);
  687.                         if (err != DOM_NO_ERR) {
  688.                                 dom_node_unref(n);
  689.                                 return CSS_OK;
  690.                         }
  691.  
  692.                         if (dom_string_caseless_lwc_isequal(name,
  693.                                         qname->name)) {
  694.                                 dom_string_unref(name);
  695.                                 dom_node_unref(n);
  696.                                 *sibling = n;
  697.                                 break;
  698.                         }
  699.                         dom_string_unref(name);
  700.                 }
  701.  
  702.                 err = dom_node_get_previous_sibling(n, &prev);
  703.                 if (err != DOM_NO_ERR) {
  704.                         dom_node_unref(n);
  705.                         return CSS_OK;
  706.                 }
  707.  
  708.                 dom_node_unref(n);
  709.                 n = prev;
  710.         }
  711.  
  712.         return CSS_OK;
  713. }
  714.  
  715. /**
  716.  * Callback to retrieve the parent of a node.
  717.  *
  718.  * \param pw      HTML document
  719.  * \param node    DOM node
  720.  * \param parent  Pointer to location to receive parent
  721.  * \return CSS_OK.
  722.  *
  723.  * \post \a parent will contain the result, or NULL if there is no match
  724.  */
  725. css_error parent_node(void *pw, void *node, void **parent)
  726. {
  727.         dom_element_parent_node(node, (struct dom_element **)parent);
  728.  
  729.         return CSS_OK;
  730. }
  731.  
  732. /**
  733.  * Callback to retrieve the preceding sibling of a node.
  734.  *
  735.  * \param pw       HTML document
  736.  * \param node     DOM node
  737.  * \param sibling  Pointer to location to receive sibling
  738.  * \return CSS_OK.
  739.  *
  740.  * \post \a sibling will contain the result, or NULL if there is no match
  741.  */
  742. css_error sibling_node(void *pw, void *node, void **sibling)
  743. {
  744.         dom_node *n = node;
  745.         dom_node *prev;
  746.         dom_exception err;
  747.  
  748.         *sibling = NULL;
  749.  
  750.         /* Find sibling element */
  751.         err = dom_node_get_previous_sibling(n, &n);
  752.         if (err != DOM_NO_ERR)
  753.                 return CSS_OK;
  754.  
  755.         while (n != NULL) {
  756.                 dom_node_type type;
  757.  
  758.                 err = dom_node_get_node_type(n, &type);
  759.                 if (err != DOM_NO_ERR) {
  760.                         dom_node_unref(n);
  761.                         return CSS_OK;
  762.                 }
  763.  
  764.                 if (type == DOM_ELEMENT_NODE)
  765.                         break;
  766.  
  767.                 err = dom_node_get_previous_sibling(n, &prev);
  768.                 if (err != DOM_NO_ERR) {
  769.                         dom_node_unref(n);
  770.                         return CSS_OK;
  771.                 }
  772.  
  773.                 dom_node_unref(n);
  774.                 n = prev;
  775.         }
  776.  
  777.         if (n != NULL) {
  778.                 /** \todo Sort out reference counting */
  779.                 dom_node_unref(n);
  780.  
  781.                 *sibling = n;
  782.         }
  783.  
  784.         return CSS_OK;
  785. }
  786.  
  787. /**
  788.  * Callback to determine if a node has the given name.
  789.  *
  790.  * \param pw     HTML document
  791.  * \param node   DOM node
  792.  * \param qname  Name to match
  793.  * \param match  Pointer to location to receive result
  794.  * \return CSS_OK.
  795.  *
  796.  * \post \a match will contain true if the node matches and false otherwise.
  797.  */
  798. css_error node_has_name(void *pw, void *node,
  799.                 const css_qname *qname, bool *match)
  800. {
  801.         nscss_select_ctx *ctx = pw;
  802.         dom_node *n = node;
  803.  
  804.         if (lwc_string_isequal(qname->name, ctx->universal, match) == lwc_error_ok && *match == false) {
  805.                 dom_string *name;
  806.                 dom_exception err;
  807.  
  808.                 err = dom_node_get_node_name(n, &name);
  809.                 if (err != DOM_NO_ERR)
  810.                         return CSS_OK;
  811.  
  812.                 /* Element names are case insensitive in HTML */
  813.                 *match = dom_string_caseless_lwc_isequal(name, qname->name);
  814.  
  815.                 dom_string_unref(name);
  816.         }
  817.  
  818.         return CSS_OK;
  819. }
  820.  
  821. /**
  822.  * Callback to determine if a node has the given class.
  823.  *
  824.  * \param pw     HTML document
  825.  * \param node   DOM node
  826.  * \param name   Name to match
  827.  * \param match  Pointer to location to receive result
  828.  * \return CSS_OK.
  829.  *
  830.  * \post \a match will contain true if the node matches and false otherwise.
  831.  */
  832. css_error node_has_class(void *pw, void *node,
  833.                 lwc_string *name, bool *match)
  834. {
  835.         dom_node *n = node;
  836.         dom_exception err;
  837.  
  838.         /** \todo: Ensure that libdom performs case-insensitive
  839.          * matching in quirks mode */
  840.         err = dom_element_has_class(n, name, match);
  841.  
  842.         assert(err == DOM_NO_ERR);
  843.  
  844.         return CSS_OK;
  845. }
  846.  
  847. /**
  848.  * Callback to determine if a node has the given id.
  849.  *
  850.  * \param pw     HTML document
  851.  * \param node   DOM node
  852.  * \param name   Name to match
  853.  * \param match  Pointer to location to receive result
  854.  * \return CSS_OK.
  855.  *
  856.  * \post \a match will contain true if the node matches and false otherwise.
  857.  */
  858. css_error node_has_id(void *pw, void *node,
  859.                 lwc_string *name, bool *match)
  860. {
  861.         dom_node *n = node;
  862.         dom_string *attr;
  863.         dom_exception err;
  864.  
  865.         *match = false;
  866.  
  867.         /** \todo Assumes an HTML DOM */
  868.         err = dom_html_element_get_id(n, &attr);
  869.         if (err != DOM_NO_ERR)
  870.                 return CSS_OK;
  871.  
  872.         if (attr != NULL) {
  873.                 *match = dom_string_lwc_isequal(attr, name);
  874.  
  875.                 dom_string_unref(attr);
  876.         }
  877.  
  878.         return CSS_OK;
  879. }
  880.  
  881. /**
  882.  * Callback to determine if a node has an attribute with the given name.
  883.  *
  884.  * \param pw     HTML document
  885.  * \param node   DOM node
  886.  * \param qname  Name to match
  887.  * \param match  Pointer to location to receive result
  888.  * \return CSS_OK on success,
  889.  *         CSS_NOMEM on memory exhaustion.
  890.  *
  891.  * \post \a match will contain true if the node matches and false otherwise.
  892.  */
  893. css_error node_has_attribute(void *pw, void *node,
  894.                 const css_qname *qname, bool *match)
  895. {
  896.         dom_node *n = node;
  897.         dom_string *name;
  898.         dom_exception err;
  899.  
  900.         err = dom_string_create_interned(
  901.                         (const uint8_t *) lwc_string_data(qname->name),
  902.                         lwc_string_length(qname->name), &name);
  903.         if (err != DOM_NO_ERR)
  904.                 return CSS_NOMEM;
  905.  
  906.         err = dom_element_has_attribute(n, name, match);
  907.         if (err != DOM_NO_ERR) {
  908.                 dom_string_unref(name);
  909.                 return CSS_OK;
  910.         }
  911.  
  912.         dom_string_unref(name);
  913.  
  914.         return CSS_OK;
  915. }
  916.  
  917. /**
  918.  * Callback to determine if a node has an attribute with given name and value.
  919.  *
  920.  * \param pw     HTML document
  921.  * \param node   DOM node
  922.  * \param qname  Name to match
  923.  * \param value  Value to match
  924.  * \param match  Pointer to location to receive result
  925.  * \return CSS_OK on success,
  926.  *         CSS_NOMEM on memory exhaustion.
  927.  *
  928.  * \post \a match will contain true if the node matches and false otherwise.
  929.  */
  930. css_error node_has_attribute_equal(void *pw, void *node,
  931.                 const css_qname *qname, lwc_string *value,
  932.                 bool *match)
  933. {
  934.         dom_node *n = node;
  935.         dom_string *name;
  936.         dom_string *atr_val;
  937.         dom_exception err;
  938.  
  939.         size_t vlen = lwc_string_length(value);
  940.  
  941.         if (vlen == 0) {
  942.                 *match = false;
  943.                 return CSS_OK;
  944.         }
  945.  
  946.         err = dom_string_create_interned(
  947.                 (const uint8_t *) lwc_string_data(qname->name),
  948.                 lwc_string_length(qname->name), &name);
  949.         if (err != DOM_NO_ERR)
  950.                 return CSS_NOMEM;
  951.  
  952.         err = dom_element_get_attribute(n, name, &atr_val);
  953.         if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
  954.                 dom_string_unref(name);
  955.                 *match = false;
  956.                 return CSS_OK;
  957.         }
  958.  
  959.         dom_string_unref(name);
  960.  
  961.         *match = dom_string_caseless_lwc_isequal(atr_val, value);
  962.  
  963.         dom_string_unref(atr_val);
  964.  
  965.         return CSS_OK;
  966. }
  967.  
  968. /**
  969.  * Callback to determine if a node has an attribute with the given name whose
  970.  * value dashmatches that given.
  971.  *
  972.  * \param pw     HTML document
  973.  * \param node   DOM node
  974.  * \param qname  Name to match
  975.  * \param value  Value to match
  976.  * \param match  Pointer to location to receive result
  977.  * \return CSS_OK on success,
  978.  *         CSS_NOMEM on memory exhaustion.
  979.  *
  980.  * \post \a match will contain true if the node matches and false otherwise.
  981.  */
  982. css_error node_has_attribute_dashmatch(void *pw, void *node,
  983.                 const css_qname *qname, lwc_string *value,
  984.                 bool *match)
  985. {
  986.         dom_node *n = node;
  987.         dom_string *name;
  988.         dom_string *atr_val;
  989.         dom_exception err;
  990.  
  991.         size_t vlen = lwc_string_length(value);
  992.  
  993.         if (vlen == 0) {
  994.                 *match = false;
  995.                 return CSS_OK;
  996.         }
  997.  
  998.         err = dom_string_create_interned(
  999.                 (const uint8_t *) lwc_string_data(qname->name),
  1000.                 lwc_string_length(qname->name), &name);
  1001.         if (err != DOM_NO_ERR)
  1002.                 return CSS_NOMEM;
  1003.  
  1004.         err = dom_element_get_attribute(n, name, &atr_val);
  1005.         if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
  1006.                 dom_string_unref(name);
  1007.                 *match = false;
  1008.                 return CSS_OK;
  1009.         }
  1010.  
  1011.         dom_string_unref(name);
  1012.  
  1013.         /* check for exact match */
  1014.         *match = dom_string_caseless_lwc_isequal(atr_val, value);
  1015.  
  1016.         /* check for dashmatch */
  1017.         if (*match == false) {
  1018.                 const char *vdata = lwc_string_data(value);
  1019.                 const char *data = (const char *) dom_string_data(atr_val);
  1020.                 size_t len = dom_string_byte_length(atr_val);
  1021.  
  1022.                 if (len > vlen && data[vlen] == '-' &&
  1023.                     strncasecmp(data, vdata, vlen) == 0) {
  1024.                                 *match = true;
  1025.                 }
  1026.         }
  1027.  
  1028.         dom_string_unref(atr_val);
  1029.  
  1030.         return CSS_OK;
  1031. }
  1032.  
  1033. /**
  1034.  * Callback to determine if a node has an attribute with the given name whose
  1035.  * value includes that given.
  1036.  *
  1037.  * \param pw     HTML document
  1038.  * \param node   DOM node
  1039.  * \param qname  Name to match
  1040.  * \param value  Value to match
  1041.  * \param match  Pointer to location to receive result
  1042.  * \return CSS_OK on success,
  1043.  *         CSS_NOMEM on memory exhaustion.
  1044.  *
  1045.  * \post \a match will contain true if the node matches and false otherwise.
  1046.  */
  1047. css_error node_has_attribute_includes(void *pw, void *node,
  1048.                 const css_qname *qname, lwc_string *value,
  1049.                 bool *match)
  1050. {
  1051.         dom_node *n = node;
  1052.         dom_string *name;
  1053.         dom_string *atr_val;
  1054.         dom_exception err;
  1055.         size_t vlen = lwc_string_length(value);
  1056.         const char *p;
  1057.         const char *start;
  1058.         const char *end;
  1059.  
  1060.         *match = false;
  1061.  
  1062.         if (vlen == 0) {
  1063.                 return CSS_OK;
  1064.         }
  1065.  
  1066.         err = dom_string_create_interned(
  1067.                 (const uint8_t *) lwc_string_data(qname->name),
  1068.                 lwc_string_length(qname->name), &name);
  1069.         if (err != DOM_NO_ERR)
  1070.                 return CSS_NOMEM;
  1071.  
  1072.         err = dom_element_get_attribute(n, name, &atr_val);
  1073.         if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
  1074.                 dom_string_unref(name);
  1075.                 *match = false;
  1076.                 return CSS_OK;
  1077.         }
  1078.  
  1079.         dom_string_unref(name);
  1080.  
  1081.         /* check for match */
  1082.         start = (const char *) dom_string_data(atr_val);
  1083.         end = start + dom_string_byte_length(atr_val);
  1084.  
  1085.         for (p = start; p <= end; p++) {
  1086.                 if (*p == ' ' || *p == '\0') {
  1087.                         if ((size_t) (p - start) == vlen &&
  1088.                             strncasecmp(start,
  1089.                                         lwc_string_data(value),
  1090.                                         vlen) == 0) {
  1091.                                 *match = true;
  1092.                                 break;
  1093.                         }
  1094.  
  1095.                         start = p + 1;
  1096.                 }
  1097.         }
  1098.  
  1099.         dom_string_unref(atr_val);
  1100.  
  1101.         return CSS_OK;
  1102. }
  1103.  
  1104. /**
  1105.  * Callback to determine if a node has an attribute with the given name whose
  1106.  * value has the prefix given.
  1107.  *
  1108.  * \param pw     HTML document
  1109.  * \param node   DOM node
  1110.  * \param qname  Name to match
  1111.  * \param value  Value to match
  1112.  * \param match  Pointer to location to receive result
  1113.  * \return CSS_OK on success,
  1114.  *         CSS_NOMEM on memory exhaustion.
  1115.  *
  1116.  * \post \a match will contain true if the node matches and false otherwise.
  1117.  */
  1118. css_error node_has_attribute_prefix(void *pw, void *node,
  1119.                 const css_qname *qname, lwc_string *value,
  1120.                 bool *match)
  1121. {
  1122.         dom_node *n = node;
  1123.         dom_string *name;
  1124.         dom_string *atr_val;
  1125.         dom_exception err;
  1126.  
  1127.         size_t vlen = lwc_string_length(value);
  1128.  
  1129.         if (vlen == 0) {
  1130.                 *match = false;
  1131.                 return CSS_OK;
  1132.         }
  1133.  
  1134.         err = dom_string_create_interned(
  1135.                 (const uint8_t *) lwc_string_data(qname->name),
  1136.                 lwc_string_length(qname->name), &name);
  1137.         if (err != DOM_NO_ERR)
  1138.                 return CSS_NOMEM;
  1139.  
  1140.         err = dom_element_get_attribute(n, name, &atr_val);
  1141.         if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
  1142.                 dom_string_unref(name);
  1143.                 *match = false;
  1144.                 return CSS_OK;
  1145.         }
  1146.  
  1147.         dom_string_unref(name);
  1148.  
  1149.         /* check for exact match */
  1150.         *match = dom_string_caseless_lwc_isequal(atr_val, value);
  1151.  
  1152.         /* check for prefix match */
  1153.         if (*match == false) {
  1154.                 const char *data = (const char *) dom_string_data(atr_val);
  1155.                 size_t len = dom_string_byte_length(atr_val);
  1156.  
  1157.                 if ((len >= vlen) &&
  1158.                     (strncasecmp(data, lwc_string_data(value), vlen) == 0)) {
  1159.                         *match = true;
  1160.                 }
  1161.         }
  1162.  
  1163.         dom_string_unref(atr_val);
  1164.  
  1165.         return CSS_OK;
  1166. }
  1167.  
  1168. /**
  1169.  * Callback to determine if a node has an attribute with the given name whose
  1170.  * value has the suffix given.
  1171.  *
  1172.  * \param pw     HTML document
  1173.  * \param node   DOM node
  1174.  * \param qname  Name to match
  1175.  * \param value  Value to match
  1176.  * \param match  Pointer to location to receive result
  1177.  * \return CSS_OK on success,
  1178.  *         CSS_NOMEM on memory exhaustion.
  1179.  *
  1180.  * \post \a match will contain true if the node matches and false otherwise.
  1181.  */
  1182. css_error node_has_attribute_suffix(void *pw, void *node,
  1183.                 const css_qname *qname, lwc_string *value,
  1184.                 bool *match)
  1185. {
  1186.         dom_node *n = node;
  1187.         dom_string *name;
  1188.         dom_string *atr_val;
  1189.         dom_exception err;
  1190.  
  1191.         size_t vlen = lwc_string_length(value);
  1192.  
  1193.         if (vlen == 0) {
  1194.                 *match = false;
  1195.                 return CSS_OK;
  1196.         }
  1197.  
  1198.         err = dom_string_create_interned(
  1199.                 (const uint8_t *) lwc_string_data(qname->name),
  1200.                 lwc_string_length(qname->name), &name);
  1201.         if (err != DOM_NO_ERR)
  1202.                 return CSS_NOMEM;
  1203.  
  1204.         err = dom_element_get_attribute(n, name, &atr_val);
  1205.         if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
  1206.                 dom_string_unref(name);
  1207.                 *match = false;
  1208.                 return CSS_OK;
  1209.         }
  1210.  
  1211.         dom_string_unref(name);
  1212.  
  1213.         /* check for exact match */
  1214.         *match = dom_string_caseless_lwc_isequal(atr_val, value);
  1215.  
  1216.         /* check for prefix match */
  1217.         if (*match == false) {
  1218.                 const char *data = (const char *) dom_string_data(atr_val);
  1219.                 size_t len = dom_string_byte_length(atr_val);
  1220.                
  1221.                 const char *start = (char *) data + len - vlen;
  1222.  
  1223.                 if ((len >= vlen) &&
  1224.                     (strncasecmp(start, lwc_string_data(value), vlen) == 0)) {
  1225.                         *match = true;
  1226.                 }
  1227.  
  1228.  
  1229.         }
  1230.  
  1231.         dom_string_unref(atr_val);
  1232.  
  1233.         return CSS_OK;
  1234. }
  1235.  
  1236. /**
  1237.  * Callback to determine if a node has an attribute with the given name whose
  1238.  * value contains the substring given.
  1239.  *
  1240.  * \param pw     HTML document
  1241.  * \param node   DOM node
  1242.  * \param qname  Name to match
  1243.  * \param value  Value to match
  1244.  * \param match  Pointer to location to receive result
  1245.  * \return CSS_OK on success,
  1246.  *         CSS_NOMEM on memory exhaustion.
  1247.  *
  1248.  * \post \a match will contain true if the node matches and false otherwise.
  1249.  */
  1250. css_error node_has_attribute_substring(void *pw, void *node,
  1251.                 const css_qname *qname, lwc_string *value,
  1252.                 bool *match)
  1253. {
  1254.         dom_node *n = node;
  1255.         dom_string *name;
  1256.         dom_string *atr_val;
  1257.         dom_exception err;
  1258.  
  1259.         size_t vlen = lwc_string_length(value);
  1260.  
  1261.         if (vlen == 0) {
  1262.                 *match = false;
  1263.                 return CSS_OK;
  1264.         }
  1265.  
  1266.         err = dom_string_create_interned(
  1267.                 (const uint8_t *) lwc_string_data(qname->name),
  1268.                 lwc_string_length(qname->name), &name);
  1269.         if (err != DOM_NO_ERR)
  1270.                 return CSS_NOMEM;
  1271.  
  1272.         err = dom_element_get_attribute(n, name, &atr_val);
  1273.         if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
  1274.                 dom_string_unref(name);
  1275.                 *match = false;
  1276.                 return CSS_OK;
  1277.         }
  1278.  
  1279.         dom_string_unref(name);
  1280.  
  1281.         /* check for exact match */
  1282.         *match = dom_string_caseless_lwc_isequal(atr_val, value);
  1283.  
  1284.         /* check for prefix match */
  1285.         if (*match == false) {
  1286.                 const char *vdata = lwc_string_data(value);
  1287.                 const char *start = (const char *) dom_string_data(atr_val);
  1288.                 size_t len = dom_string_byte_length(atr_val);
  1289.                 const char *last_start = start + len - vlen;
  1290.  
  1291.                 if (len >= vlen) {
  1292.                         while (start <= last_start) {
  1293.                                 if (strncasecmp(start, vdata,
  1294.                                                 vlen) == 0) {
  1295.                                         *match = true;
  1296.                                         break;
  1297.                                 }
  1298.  
  1299.                                 start++;
  1300.                         }
  1301.                 }
  1302.         }
  1303.  
  1304.         dom_string_unref(atr_val);
  1305.  
  1306.         return CSS_OK;
  1307. }
  1308.  
  1309. /**
  1310.  * Callback to determine if a node is the root node of the document.
  1311.  *
  1312.  * \param pw     HTML document
  1313.  * \param node   DOM node
  1314.  * \param match  Pointer to location to receive result
  1315.  * \return CSS_OK.
  1316.  *
  1317.  * \post \a match will contain true if the node matches and false otherwise.
  1318.  */
  1319. css_error node_is_root(void *pw, void *node, bool *match)
  1320. {
  1321.         dom_node *n = node;
  1322.         dom_node *parent;
  1323.         dom_node_type type;
  1324.         dom_exception err;
  1325.  
  1326.         err = dom_node_get_parent_node(n, &parent);
  1327.         if (err != DOM_NO_ERR) {
  1328.                 return CSS_NOMEM;
  1329.         }
  1330.  
  1331.         if (parent != NULL) {
  1332.                 err = dom_node_get_node_type(parent, &type);
  1333.  
  1334.                 dom_node_unref(parent);
  1335.  
  1336.                 if (err != DOM_NO_ERR)
  1337.                         return CSS_NOMEM;
  1338.  
  1339.                 if (type != DOM_DOCUMENT_NODE) {
  1340.                         *match = false;
  1341.                         return CSS_OK;
  1342.                 }
  1343.         }
  1344.        
  1345.         *match = true;
  1346.  
  1347.         return CSS_OK;
  1348. }
  1349.  
  1350. static int
  1351. node_count_siblings_check(dom_node *node,
  1352.                           bool check_name,
  1353.                           dom_string *name)
  1354. {
  1355.         dom_node_type type;
  1356.         int ret = 0;
  1357.         dom_exception exc;
  1358.  
  1359.         if (node == NULL)
  1360.                 return 0;
  1361.  
  1362.         exc = dom_node_get_node_type(node, &type);
  1363.         if ((exc != DOM_NO_ERR) || (type != DOM_ELEMENT_NODE)) {
  1364.                 return 0;
  1365.         }
  1366.        
  1367.         if (check_name) {
  1368.                 dom_string *node_name = NULL;
  1369.                 exc = dom_node_get_node_name(node, &node_name);
  1370.  
  1371.                 if ((exc == DOM_NO_ERR) && (node_name != NULL)) {
  1372.  
  1373.                         if (dom_string_caseless_isequal(name,
  1374.                                                         node_name)) {
  1375.                                 ret = 1;
  1376.                         }
  1377.                         dom_string_unref(node_name);
  1378.                 }
  1379.         } else {
  1380.                 ret = 1;
  1381.         }
  1382.        
  1383.         return ret;
  1384. }
  1385.                        
  1386. /**
  1387.  * Callback to count a node's siblings.
  1388.  *
  1389.  * \param pw         HTML document
  1390.  * \param node       DOM node
  1391.  * \param same_name  Only count siblings with the same name, or all
  1392.  * \param after      Count anteceding instead of preceding siblings
  1393.  * \param count      Pointer to location to receive result
  1394.  * \return CSS_OK.
  1395.  *
  1396.  * \post \a count will contain the number of siblings
  1397.  */
  1398. css_error node_count_siblings(void *pw, void *n, bool same_name,
  1399.                 bool after, int32_t *count)
  1400. {
  1401.         int32_t cnt = 0;
  1402.         dom_exception exc;
  1403.         dom_string *node_name = NULL;
  1404.  
  1405.         if (same_name) {
  1406.                 dom_node *node = n;
  1407.                 exc = dom_node_get_node_name(node, &node_name);
  1408.                 if ((exc != DOM_NO_ERR) || (node_name == NULL)) {
  1409.                         return CSS_NOMEM;
  1410.                 }
  1411.         }
  1412.        
  1413.         if (after) {
  1414.                 dom_node *node = dom_node_ref(n);
  1415.                 dom_node *next;
  1416.                
  1417.                 do {
  1418.                         exc = dom_node_get_next_sibling(node, &next);
  1419.                         if ((exc != DOM_NO_ERR))
  1420.                                 break;
  1421.                        
  1422.                         dom_node_unref(node);
  1423.                         node = next;
  1424.  
  1425.                         cnt += node_count_siblings_check(node, same_name, node_name);
  1426.                 } while (node != NULL);
  1427.         } else {
  1428.                 dom_node *node = dom_node_ref(n);
  1429.                 dom_node *next;
  1430.                
  1431.                 do {
  1432.                         exc = dom_node_get_previous_sibling(node, &next);
  1433.                         if ((exc != DOM_NO_ERR))
  1434.                                 break;
  1435.                        
  1436.                         dom_node_unref(node);
  1437.                         node = next;
  1438.  
  1439.                         cnt += node_count_siblings_check(node, same_name, node_name);
  1440.  
  1441.                 } while (node != NULL);
  1442.         }
  1443.  
  1444.         if (node_name != NULL) {
  1445.                 dom_string_unref(node_name);   
  1446.         }
  1447.  
  1448.         *count = cnt;
  1449.         return CSS_OK;
  1450. }
  1451.  
  1452. /**
  1453.  * Callback to determine if a node is empty.
  1454.  *
  1455.  * \param pw     HTML document
  1456.  * \param node   DOM node
  1457.  * \param match  Pointer to location to receive result
  1458.  * \return CSS_OK.
  1459.  *
  1460.  * \post \a match will contain true if the node is empty and false otherwise.
  1461.  */
  1462. css_error node_is_empty(void *pw, void *node, bool *match)
  1463. {
  1464.         dom_node *n = node, *next;
  1465.         dom_exception err;
  1466.        
  1467.         *match = true;
  1468.        
  1469.         err = dom_node_get_first_child(n, &n);
  1470.         if (err != DOM_NO_ERR) {
  1471.                 return CSS_BADPARM;
  1472.         }
  1473.        
  1474.         while (n != NULL) {
  1475.                 dom_node_type ntype;
  1476.                 err = dom_node_get_node_type(n, &ntype);
  1477.                 if (err != DOM_NO_ERR) {
  1478.                         dom_node_unref(n);
  1479.                         return CSS_BADPARM;
  1480.                 }
  1481.                
  1482.                 if (ntype == DOM_ELEMENT_NODE ||
  1483.                     ntype == DOM_TEXT_NODE) {
  1484.                         *match = false;
  1485.                         dom_node_unref(n);
  1486.                         break;
  1487.                 }
  1488.                
  1489.                 err = dom_node_get_next_sibling(n, &next);
  1490.                 if (err != DOM_NO_ERR) {
  1491.                         dom_node_unref(n);
  1492.                         return CSS_BADPARM;
  1493.                 }
  1494.                 dom_node_unref(n);
  1495.                 n = next;
  1496.         }
  1497.        
  1498.         return CSS_OK;
  1499. }
  1500.  
  1501. /**
  1502.  * Callback to determine if a node is a linking element.
  1503.  *
  1504.  * \param pw     HTML document
  1505.  * \param n      DOM node
  1506.  * \param match  Pointer to location to receive result
  1507.  * \return CSS_OK.
  1508.  *
  1509.  * \post \a match will contain true if the node matches and false otherwise.
  1510.  */
  1511. css_error node_is_link(void *pw, void *n, bool *match)
  1512. {
  1513.         dom_node *node = n;
  1514.         dom_exception exc;
  1515.         dom_string *node_name = NULL;
  1516.  
  1517.         exc = dom_node_get_node_name(node, &node_name);
  1518.         if ((exc != DOM_NO_ERR) || (node_name == NULL)) {
  1519.                 return CSS_NOMEM;
  1520.         }
  1521.  
  1522.         if (dom_string_caseless_lwc_isequal(node_name, corestring_lwc_a)) {
  1523.                 bool has_href;
  1524.                 exc = dom_element_has_attribute(node, corestring_dom_href,
  1525.                                 &has_href);
  1526.                 if ((exc == DOM_NO_ERR) && (has_href)) {
  1527.                         *match = true;
  1528.                 } else {
  1529.                         *match = false;
  1530.                 }
  1531.         } else {
  1532.                 *match = false;
  1533.         }
  1534.         dom_string_unref(node_name);
  1535.  
  1536.         return CSS_OK;
  1537. }
  1538.  
  1539. /**
  1540.  * Callback to determine if a node is a linking element whose target has been
  1541.  * visited.
  1542.  *
  1543.  * \param pw     HTML document
  1544.  * \param node   DOM node
  1545.  * \param match  Pointer to location to receive result
  1546.  * \return CSS_OK.
  1547.  *
  1548.  * \post \a match will contain true if the node matches and false otherwise.
  1549.  */
  1550. css_error node_is_visited(void *pw, void *node, bool *match)
  1551. {
  1552.         *match = false;
  1553.  
  1554.         /** \todo Implement visted check in a more performant way */
  1555.  
  1556. #ifdef SUPPORT_VISITED
  1557.         nscss_select_ctx *ctx = pw;
  1558.         xmlNode *n = node;
  1559.  
  1560.         if (strcasecmp((const char *) n->name, "a") == 0) {
  1561.                 nsurl *url;
  1562.                 nserror error;
  1563.                 const struct url_data *data;
  1564.                 xmlChar *href = xmlGetProp(n, (const xmlChar *) "href");
  1565.  
  1566.                 if (href == NULL)
  1567.                         return CSS_OK;
  1568.  
  1569.                 /* Make href absolute */
  1570.                 /* TODO: this duplicates what we do for box->href */
  1571.                 error = nsurl_join(ctx->base_url, (const char *)href, &url);
  1572.  
  1573.                 xmlFree(href);
  1574.                 if (error != NSERROR_OK) {
  1575.                         return CSS_NOMEM;
  1576.                 }
  1577.  
  1578.                 data = urldb_get_url_data(nsurl_access(url));
  1579.  
  1580.                 /* Visited if in the db and has
  1581.                  * non-zero visit count */
  1582.                 if (data != NULL && data->visits > 0)
  1583.                         *match = true;
  1584.  
  1585.                 nsurl_unref(url);
  1586.         }
  1587. #endif
  1588.  
  1589.         return CSS_OK;
  1590. }
  1591.  
  1592. /**
  1593.  * Callback to determine if a node is currently being hovered over.
  1594.  *
  1595.  * \param pw     HTML document
  1596.  * \param node   DOM node
  1597.  * \param match  Pointer to location to receive result
  1598.  * \return CSS_OK.
  1599.  *
  1600.  * \post \a match will contain true if the node matches and false otherwise.
  1601.  */
  1602. css_error node_is_hover(void *pw, void *node, bool *match)
  1603. {
  1604.         /** \todo Support hovering */
  1605.  
  1606.         *match = false;
  1607.  
  1608.         return CSS_OK;
  1609. }
  1610.  
  1611. /**
  1612.  * Callback to determine if a node is currently activated.
  1613.  *
  1614.  * \param pw     HTML document
  1615.  * \param node   DOM node
  1616.  * \param match  Pointer to location to receive result
  1617.  * \return CSS_OK.
  1618.  *
  1619.  * \post \a match will contain true if the node matches and false otherwise.
  1620.  */
  1621. css_error node_is_active(void *pw, void *node, bool *match)
  1622. {
  1623.         /** \todo Support active nodes */
  1624.  
  1625.         *match = false;
  1626.  
  1627.         return CSS_OK;
  1628. }
  1629.  
  1630. /**
  1631.  * Callback to determine if a node has the input focus.
  1632.  *
  1633.  * \param pw     HTML document
  1634.  * \param node   DOM node
  1635.  * \param match  Pointer to location to receive result
  1636.  * \return CSS_OK.
  1637.  *
  1638.  * \post \a match will contain true if the node matches and false otherwise.
  1639.  */
  1640. css_error node_is_focus(void *pw, void *node, bool *match)
  1641. {
  1642.         /** \todo Support focussed nodes */
  1643.  
  1644.         *match = false;
  1645.  
  1646.         return CSS_OK;
  1647. }
  1648.  
  1649. /**
  1650.  * Callback to determine if a node is enabled.
  1651.  *
  1652.  * \param pw     HTML document
  1653.  * \param node   DOM node
  1654.  * \param match  Pointer to location to receive result
  1655.  * \return CSS_OK.
  1656.  *
  1657.  * \post \a match with contain true if the node is enabled and false otherwise.
  1658.  */
  1659. css_error node_is_enabled(void *pw, void *node, bool *match)
  1660. {
  1661.         /** \todo Support enabled nodes */
  1662.  
  1663.         *match = false;
  1664.  
  1665.         return CSS_OK;
  1666. }
  1667.  
  1668. /**
  1669.  * Callback to determine if a node is disabled.
  1670.  *
  1671.  * \param pw     HTML document
  1672.  * \param node   DOM node
  1673.  * \param match  Pointer to location to receive result
  1674.  * \return CSS_OK.
  1675.  *
  1676.  * \post \a match with contain true if the node is disabled and false otherwise.
  1677.  */
  1678. css_error node_is_disabled(void *pw, void *node, bool *match)
  1679. {
  1680.         /** \todo Support disabled nodes */
  1681.  
  1682.         *match = false;
  1683.  
  1684.         return CSS_OK;
  1685. }
  1686.  
  1687. /**
  1688.  * Callback to determine if a node is checked.
  1689.  *
  1690.  * \param pw     HTML document
  1691.  * \param node   DOM node
  1692.  * \param match  Pointer to location to receive result
  1693.  * \return CSS_OK.
  1694.  *
  1695.  * \post \a match with contain true if the node is checked and false otherwise.
  1696.  */
  1697. css_error node_is_checked(void *pw, void *node, bool *match)
  1698. {
  1699.         /** \todo Support checked nodes */
  1700.  
  1701.         *match = false;
  1702.  
  1703.         return CSS_OK;
  1704. }
  1705.  
  1706. /**
  1707.  * Callback to determine if a node is the target of the document URL.
  1708.  *
  1709.  * \param pw     HTML document
  1710.  * \param node   DOM node
  1711.  * \param match  Pointer to location to receive result
  1712.  * \return CSS_OK.
  1713.  *
  1714.  * \post \a match with contain true if the node matches and false otherwise.
  1715.  */
  1716. css_error node_is_target(void *pw, void *node, bool *match)
  1717. {
  1718.         /** \todo Support target */
  1719.  
  1720.         *match = false;
  1721.  
  1722.         return CSS_OK;
  1723. }
  1724.  
  1725. /**
  1726.  * Callback to determine if a node has the given language
  1727.  *
  1728.  * \param pw     HTML document
  1729.  * \param node   DOM node
  1730.  * \param lang   Language specifier to match
  1731.  * \param match  Pointer to location to receive result
  1732.  * \return CSS_OK.
  1733.  *
  1734.  * \post \a match will contain true if the node matches and false otherwise.
  1735.  */
  1736. css_error node_is_lang(void *pw, void *node,
  1737.                 lwc_string *lang, bool *match)
  1738. {
  1739.         /** \todo Support languages */
  1740.  
  1741.         *match = false;
  1742.  
  1743.         return CSS_OK;
  1744. }
  1745.  
  1746. static css_error
  1747. node_presentational_hint_vertical_align(nscss_select_ctx *ctx,
  1748.                                           dom_node *node,
  1749.                                           css_hint *hint)
  1750. {
  1751.         dom_string *name;
  1752.         dom_string *valign = NULL;
  1753.         dom_exception err;
  1754.  
  1755.         err = dom_node_get_node_name(node, &name);
  1756.         if (err != DOM_NO_ERR)
  1757.                 return CSS_PROPERTY_NOT_SET;
  1758.  
  1759.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_col) ||
  1760.             dom_string_caseless_lwc_isequal(name, corestring_lwc_thead) ||
  1761.             dom_string_caseless_lwc_isequal(name, corestring_lwc_tbody) ||
  1762.             dom_string_caseless_lwc_isequal(name, corestring_lwc_tfoot) ||
  1763.             dom_string_caseless_lwc_isequal(name, corestring_lwc_tr) ||
  1764.             dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
  1765.             dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
  1766.                 err = dom_element_get_attribute(node,
  1767.                                 corestring_dom_valign, &valign);
  1768.                 if (err != DOM_NO_ERR || valign == NULL) {
  1769.                         dom_string_unref(name);
  1770.                         return CSS_PROPERTY_NOT_SET;
  1771.                 }
  1772.  
  1773.                 if (dom_string_caseless_lwc_isequal(valign,
  1774.                                 corestring_lwc_top)) {
  1775.                         hint->status = CSS_VERTICAL_ALIGN_TOP;
  1776.                 } else if (dom_string_caseless_lwc_isequal(valign,
  1777.                                         corestring_lwc_middle)) {
  1778.                         hint->status = CSS_VERTICAL_ALIGN_MIDDLE;
  1779.                 } else if (dom_string_caseless_lwc_isequal(valign,
  1780.                                         corestring_lwc_bottom)) {
  1781.                         hint->status = CSS_VERTICAL_ALIGN_BOTTOM;
  1782.                 } else if (dom_string_caseless_lwc_isequal(valign,
  1783.                                         corestring_lwc_baseline)) {
  1784.                         hint->status = CSS_VERTICAL_ALIGN_BASELINE;
  1785.                 } else {
  1786.                         dom_string_unref(valign);
  1787.                         dom_string_unref(name);
  1788.                         return CSS_PROPERTY_NOT_SET;
  1789.                 }
  1790.  
  1791.                 dom_string_unref(valign);
  1792.                 dom_string_unref(name);
  1793.  
  1794.                 return CSS_OK;
  1795.         } else if (dom_string_caseless_lwc_isequal(name,
  1796.                                 corestring_lwc_applet) ||
  1797.                    dom_string_caseless_lwc_isequal(name,
  1798.                                 corestring_lwc_embed) ||
  1799.                    dom_string_caseless_lwc_isequal(name,
  1800.                                 corestring_lwc_iframe) ||
  1801.                    dom_string_caseless_lwc_isequal(name,
  1802.                                 corestring_lwc_img) ||
  1803.                    dom_string_caseless_lwc_isequal(name,
  1804.                                 corestring_lwc_object)) {
  1805.                 /** \todo input[type=image][align=*] - $11.3.3 */
  1806.                 err = dom_element_get_attribute(node,
  1807.                                 corestring_dom_align, &valign);
  1808.                 if (err != DOM_NO_ERR || valign == NULL) {
  1809.                         dom_string_unref(name);
  1810.                         return CSS_PROPERTY_NOT_SET;
  1811.                 }
  1812.  
  1813.                 if (dom_string_caseless_lwc_isequal(valign,
  1814.                                 corestring_lwc_top)) {
  1815.                         hint->status = CSS_VERTICAL_ALIGN_TOP;
  1816.                 } else if (dom_string_caseless_lwc_isequal(valign,
  1817.                                         corestring_lwc_bottom) ||
  1818.                            dom_string_caseless_lwc_isequal(valign,
  1819.                                         corestring_lwc_baseline)) {
  1820.                         hint->status = CSS_VERTICAL_ALIGN_BASELINE;
  1821.                 } else if (dom_string_caseless_lwc_isequal(valign,
  1822.                                         corestring_lwc_texttop)) {
  1823.                         hint->status = CSS_VERTICAL_ALIGN_TEXT_TOP;
  1824.                 } else if (dom_string_caseless_lwc_isequal(valign,
  1825.                                         corestring_lwc_absmiddle) ||
  1826.                            dom_string_caseless_lwc_isequal(valign,
  1827.                                         corestring_lwc_abscenter)) {
  1828.                         hint->status = CSS_VERTICAL_ALIGN_MIDDLE;
  1829.                 } else {
  1830.                         dom_string_unref(valign);
  1831.                         dom_string_unref(name);
  1832.                         return CSS_PROPERTY_NOT_SET;
  1833.                 }
  1834.  
  1835.                 dom_string_unref(valign);
  1836.                 dom_string_unref(name);
  1837.  
  1838.                 return CSS_OK;
  1839.         }
  1840.  
  1841.         dom_string_unref(name);
  1842.         return CSS_PROPERTY_NOT_SET;
  1843. }
  1844.  
  1845. static css_error
  1846. node_presentational_hint_text_align(nscss_select_ctx *ctx,
  1847.                                           dom_node *node,
  1848.                                           css_hint *hint)
  1849. {
  1850.         dom_string *name;
  1851.         dom_string *align = NULL;
  1852.         dom_exception err;
  1853.  
  1854.         err = dom_node_get_node_name(node, &name);
  1855.         if (err != DOM_NO_ERR)
  1856.                 return CSS_PROPERTY_NOT_SET;
  1857.  
  1858.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_p) ||
  1859.             dom_string_caseless_lwc_isequal(name, corestring_lwc_h1) ||
  1860.             dom_string_caseless_lwc_isequal(name, corestring_lwc_h2) ||
  1861.             dom_string_caseless_lwc_isequal(name, corestring_lwc_h3) ||
  1862.             dom_string_caseless_lwc_isequal(name, corestring_lwc_h4) ||
  1863.             dom_string_caseless_lwc_isequal(name, corestring_lwc_h5) ||
  1864.             dom_string_caseless_lwc_isequal(name, corestring_lwc_h6)) {
  1865.                 err = dom_element_get_attribute(node,
  1866.                                 corestring_dom_align, &align);
  1867.                 if (err != DOM_NO_ERR || align == NULL) {
  1868.                         dom_string_unref(name);
  1869.                         return CSS_PROPERTY_NOT_SET;
  1870.                 }
  1871.  
  1872.                 if (dom_string_caseless_lwc_isequal(align,
  1873.                                 corestring_lwc_left)) {
  1874.                         hint->status = CSS_TEXT_ALIGN_LEFT;
  1875.                 } else if (dom_string_caseless_lwc_isequal(align,
  1876.                                         corestring_lwc_center)) {
  1877.                         hint->status = CSS_TEXT_ALIGN_CENTER;
  1878.                 } else if (dom_string_caseless_lwc_isequal(align,
  1879.                                         corestring_lwc_right)) {
  1880.                         hint->status = CSS_TEXT_ALIGN_RIGHT;
  1881.                 } else if (dom_string_caseless_lwc_isequal(align,
  1882.                                         corestring_lwc_justify)) {
  1883.                         hint->status = CSS_TEXT_ALIGN_JUSTIFY;
  1884.                 } else {
  1885.                         dom_string_unref(align);
  1886.                         dom_string_unref(name);
  1887.                         return CSS_PROPERTY_NOT_SET;
  1888.                 }
  1889.  
  1890.                 dom_string_unref(align);
  1891.                 dom_string_unref(name);
  1892.  
  1893.                 return CSS_OK;
  1894.         } else if (dom_string_caseless_lwc_isequal(name,
  1895.                                 corestring_lwc_center)) {
  1896.                 hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER;
  1897.  
  1898.                 dom_string_unref(name);
  1899.  
  1900.                 return CSS_OK;
  1901.         } else if (dom_string_caseless_lwc_isequal(name,
  1902.                                 corestring_lwc_caption)) {
  1903.                 err = dom_element_get_attribute(node,
  1904.                                 corestring_dom_align, &align);
  1905.                 if (err != DOM_NO_ERR) {
  1906.                         dom_string_unref(name);
  1907.                         return CSS_PROPERTY_NOT_SET;
  1908.                 }
  1909.  
  1910.                 if (align == NULL || dom_string_caseless_lwc_isequal(align,
  1911.                                 corestring_lwc_center)) {
  1912.                         hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER;
  1913.                 } else if (dom_string_caseless_lwc_isequal(align,
  1914.                                         corestring_lwc_left)) {
  1915.                         hint->status = CSS_TEXT_ALIGN_LIBCSS_LEFT;
  1916.                 } else if (dom_string_caseless_lwc_isequal(align,
  1917.                                         corestring_lwc_right)) {
  1918.                         hint->status = CSS_TEXT_ALIGN_LIBCSS_RIGHT;
  1919.                 } else if (dom_string_caseless_lwc_isequal(align,
  1920.                                         corestring_lwc_justify)) {
  1921.                         hint->status = CSS_TEXT_ALIGN_JUSTIFY;
  1922.                 } else {
  1923.                         dom_string_unref(align);
  1924.                         dom_string_unref(name);
  1925.                         return CSS_PROPERTY_NOT_SET;
  1926.                 }
  1927.  
  1928.                 if (align != NULL)
  1929.                         dom_string_unref(align);
  1930.                 dom_string_unref(name);
  1931.  
  1932.                 return CSS_OK;
  1933.         } else if (dom_string_caseless_lwc_isequal(name,
  1934.                                 corestring_lwc_div) ||
  1935.                    dom_string_caseless_lwc_isequal(name,
  1936.                                 corestring_lwc_thead) ||
  1937.                    dom_string_caseless_lwc_isequal(name,
  1938.                                 corestring_lwc_tbody) ||
  1939.                    dom_string_caseless_lwc_isequal(name,
  1940.                                 corestring_lwc_tfoot) ||
  1941.                    dom_string_caseless_lwc_isequal(name,
  1942.                                 corestring_lwc_tr) ||
  1943.                    dom_string_caseless_lwc_isequal(name,
  1944.                                 corestring_lwc_td) ||
  1945.                    dom_string_caseless_lwc_isequal(name,
  1946.                                 corestring_lwc_th)) {
  1947.                 err = dom_element_get_attribute(node,
  1948.                                 corestring_dom_align, &align);
  1949.                 if (err != DOM_NO_ERR || align == NULL) {
  1950.                         dom_string_unref(name);
  1951.                         return CSS_PROPERTY_NOT_SET;
  1952.                 }
  1953.  
  1954.                 if (dom_string_caseless_lwc_isequal(align,
  1955.                                         corestring_lwc_center)) {
  1956.                         hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER;
  1957.                 } else if (dom_string_caseless_lwc_isequal(align,
  1958.                                         corestring_lwc_left)) {
  1959.                         hint->status = CSS_TEXT_ALIGN_LIBCSS_LEFT;
  1960.                 } else if (dom_string_caseless_lwc_isequal(align,
  1961.                                         corestring_lwc_right)) {
  1962.                         hint->status = CSS_TEXT_ALIGN_LIBCSS_RIGHT;
  1963.                 } else if (dom_string_caseless_lwc_isequal(align,
  1964.                                         corestring_lwc_justify)) {
  1965.                         hint->status = CSS_TEXT_ALIGN_JUSTIFY;
  1966.                 } else {
  1967.                         dom_string_unref(align);
  1968.                         dom_string_unref(name);
  1969.                         return CSS_PROPERTY_NOT_SET;
  1970.                 }
  1971.  
  1972.                 dom_string_unref(align);
  1973.                 dom_string_unref(name);
  1974.  
  1975.                 return CSS_OK;
  1976.         } else if (dom_string_caseless_lwc_isequal(name,
  1977.                         corestring_lwc_table)) {
  1978.                 /* Tables usually reset alignment */
  1979.                 hint->status = CSS_TEXT_ALIGN_INHERIT_IF_NON_MAGIC;
  1980.  
  1981.                 dom_string_unref(name);
  1982.  
  1983.                 return CSS_OK;
  1984.         } else {
  1985.                 dom_string_unref(name);
  1986.  
  1987.                 return CSS_PROPERTY_NOT_SET;
  1988.         }
  1989.  
  1990. }
  1991.  
  1992. static css_error
  1993. node_presentational_hint_padding_trbl(nscss_select_ctx *ctx,
  1994.                                           dom_node *node,
  1995.                                           css_hint *hint)
  1996. {
  1997.         dom_string *name;
  1998.         dom_exception exc;
  1999.         dom_string *cellpadding = NULL;
  2000.        
  2001.         exc = dom_node_get_node_name(node, &name);
  2002.         if (exc != DOM_NO_ERR)
  2003.                 return CSS_BADPARM;
  2004.        
  2005.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
  2006.             dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
  2007.                 css_qname qs;
  2008.                 dom_node *tablenode = NULL;
  2009.                 qs.ns = NULL;
  2010.                 qs.name = lwc_string_ref(corestring_lwc_table);
  2011.                 if (named_ancestor_node(ctx, node, &qs,
  2012.                                         (void *)&tablenode) != CSS_OK) {
  2013.                         /* Didn't find, or had error */
  2014.                         dom_string_unref(name);
  2015.                         return CSS_PROPERTY_NOT_SET;
  2016.                 }
  2017.                
  2018.                 lwc_string_unref(qs.name);
  2019.                
  2020.                 if (tablenode != NULL) {
  2021.                         exc = dom_element_get_attribute(tablenode,
  2022.                                         corestring_dom_cellpadding,
  2023.                                         &cellpadding);
  2024.                         if (exc != DOM_NO_ERR) {
  2025.                                 dom_string_unref(name);
  2026.                                 return CSS_BADPARM;
  2027.                         }
  2028.                 }
  2029.                 /* No need to unref tablenode, named_ancestor_node does not
  2030.                  * return a reffed node to the CSS
  2031.                  */
  2032.         }
  2033.        
  2034.         dom_string_unref(name);
  2035.        
  2036.         if (cellpadding == NULL)
  2037.                 return CSS_PROPERTY_NOT_SET;
  2038.  
  2039.         if (parse_dimension(dom_string_data(cellpadding), false,
  2040.                             &hint->data.length.value,
  2041.                             &hint->data.length.unit)) {
  2042.                 hint->status = CSS_PADDING_SET;
  2043.         } else {
  2044.                 dom_string_unref(cellpadding);
  2045.                 return CSS_PROPERTY_NOT_SET;
  2046.         }
  2047.        
  2048.         return CSS_OK;
  2049. }
  2050.  
  2051. static css_error
  2052. node_presentational_hint_margin_rl(nscss_select_ctx *ctx,
  2053.                                    dom_node *node,
  2054.                                    css_hint *hint,
  2055.                                    uint32_t property)
  2056. {
  2057.         dom_string *n;
  2058.         dom_exception exc;
  2059.        
  2060.         exc = dom_node_get_node_name(node, &n);
  2061.         if (exc != DOM_NO_ERR)
  2062.                 return CSS_BADPARM;
  2063.        
  2064.         if (dom_string_caseless_lwc_isequal(n, corestring_lwc_img) ||
  2065.             dom_string_caseless_lwc_isequal(n, corestring_lwc_applet)) {
  2066.                 dom_string_unref(n);
  2067.                 exc = dom_element_get_attribute(node,
  2068.                                 corestring_dom_hspace, &n);
  2069.                 if (exc != DOM_NO_ERR) {
  2070.                         return CSS_BADPARM;
  2071.                 }
  2072.                
  2073.                 if (n == NULL)
  2074.                         return CSS_PROPERTY_NOT_SET;
  2075.                
  2076.                 if (parse_dimension(dom_string_data(n), false,
  2077.                                     &hint->data.length.value,
  2078.                                     &hint->data.length.unit)) {
  2079.                         hint->status = CSS_MARGIN_SET;
  2080.                 } else {
  2081.                         dom_string_unref(n);
  2082.                         return CSS_PROPERTY_NOT_SET;
  2083.                 }
  2084.                 dom_string_unref(n);
  2085.                 return CSS_OK;
  2086.         } else if (dom_string_caseless_lwc_isequal(n, corestring_lwc_table) ||
  2087.                    dom_string_caseless_lwc_isequal(n, corestring_lwc_align)) {
  2088.                 dom_string_unref(n);
  2089.                 exc = dom_element_get_attribute(node,
  2090.                                 corestring_dom_align, &n);
  2091.                 if (exc != DOM_NO_ERR) {
  2092.                         return CSS_BADPARM;
  2093.                 }
  2094.                
  2095.                 if (n == NULL)
  2096.                         return CSS_PROPERTY_NOT_SET;
  2097.                
  2098.                 if (dom_string_caseless_lwc_isequal(n,
  2099.                                 corestring_lwc_center) ||
  2100.                     dom_string_caseless_lwc_isequal(n,
  2101.                                 corestring_lwc_abscenter) ||
  2102.                     dom_string_caseless_lwc_isequal(n,
  2103.                                 corestring_lwc_middle) ||
  2104.                     dom_string_caseless_lwc_isequal(n,
  2105.                                 corestring_lwc_absmiddle)) {
  2106.                         hint->status = CSS_MARGIN_AUTO;
  2107.                 } else {
  2108.                         dom_string_unref(n);
  2109.                         return CSS_PROPERTY_NOT_SET;
  2110.                 }
  2111.                
  2112.                 dom_string_unref(n);
  2113.                 return CSS_OK;
  2114.         } else if (dom_string_caseless_lwc_isequal(n, corestring_lwc_hr)) {
  2115.                 dom_string_unref(n);
  2116.                 exc = dom_element_get_attribute(node,
  2117.                                 corestring_dom_align, &n);
  2118.                 if (exc != DOM_NO_ERR)
  2119.                         return CSS_BADPARM;
  2120.                
  2121.                 if (n == NULL)
  2122.                         return CSS_PROPERTY_NOT_SET;
  2123.                
  2124.                 if (dom_string_caseless_lwc_isequal(n,
  2125.                                 corestring_lwc_left)) {
  2126.                         if (property == CSS_PROP_MARGIN_LEFT) {
  2127.                                 hint->data.length.value = 0;
  2128.                                 hint->data.length.unit = CSS_UNIT_PX;
  2129.                                 hint->status = CSS_MARGIN_SET;
  2130.                         } else {
  2131.                                 hint->status = CSS_MARGIN_AUTO;
  2132.                         }
  2133.                 } else if (dom_string_caseless_lwc_isequal(n,
  2134.                                 corestring_lwc_center)) {
  2135.                         hint->status = CSS_MARGIN_AUTO;
  2136.                 } else if (dom_string_caseless_lwc_isequal(n,
  2137.                                 corestring_lwc_right)) {
  2138.                         if (property == CSS_PROP_MARGIN_RIGHT) {
  2139.                                 hint->data.length.value = 0;
  2140.                                 hint->data.length.unit = CSS_UNIT_PX;
  2141.                                 hint->status = CSS_MARGIN_SET;
  2142.                         } else {
  2143.                                 hint->status = CSS_MARGIN_AUTO;
  2144.                         }
  2145.                 } else {
  2146.                         dom_string_unref(n);
  2147.                         return CSS_PROPERTY_NOT_SET;
  2148.                 }
  2149.                 dom_string_unref(n);
  2150.                
  2151.                 return CSS_OK;
  2152.         }
  2153.        
  2154.         dom_string_unref(n);
  2155.        
  2156.         return CSS_PROPERTY_NOT_SET;
  2157. }
  2158.  
  2159. static css_error
  2160. node_presentational_hint_margin_tb(nscss_select_ctx *ctx,
  2161.                                           dom_node *node,
  2162.                                           css_hint *hint)
  2163. {
  2164.         dom_string *name, *vspace = NULL;
  2165.         dom_exception exc;
  2166.        
  2167.         exc = dom_node_get_node_name(node, &name);
  2168.         if (exc != DOM_NO_ERR)
  2169.                 return CSS_BADPARM;
  2170.        
  2171.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_img) ||
  2172.             dom_string_caseless_lwc_isequal(name, corestring_lwc_applet)) {
  2173.                 exc = dom_element_get_attribute(node, corestring_dom_vspace,
  2174.                                 &vspace);
  2175.                 if (exc != DOM_NO_ERR) {
  2176.                         dom_string_unref(name);
  2177.                         return CSS_BADPARM;
  2178.                 }
  2179.         }
  2180.        
  2181.         dom_string_unref(name);
  2182.        
  2183.         if (vspace == NULL)
  2184.                 return CSS_PROPERTY_NOT_SET;
  2185.        
  2186.         if (parse_dimension(dom_string_data(vspace), false,
  2187.                             &hint->data.length.value,
  2188.                             &hint->data.length.unit)) {
  2189.                 hint->status = CSS_MARGIN_SET;
  2190.         } else {
  2191.                 dom_string_unref(vspace);
  2192.                 return CSS_PROPERTY_NOT_SET;
  2193.         }
  2194.  
  2195.         dom_string_unref(vspace);
  2196.  
  2197.         return CSS_OK;
  2198. }
  2199.  
  2200. static css_error
  2201. node_presentational_hint_border_trbl_width(nscss_select_ctx *ctx,
  2202.                                           dom_node *node,
  2203.                                           css_hint *hint)
  2204. {
  2205.         dom_string *name;
  2206.         dom_exception exc;
  2207.         dom_string *width = NULL;
  2208.         bool is_table_cell = false;
  2209.        
  2210.         exc = dom_node_get_node_name(node, &name);
  2211.         if (exc != DOM_NO_ERR)
  2212.                 return CSS_BADPARM;
  2213.        
  2214.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
  2215.             dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
  2216.                 css_qname qs;
  2217.                 dom_node *tablenode = NULL;
  2218.                 qs.ns = NULL;
  2219.                 qs.name = lwc_string_ref(corestring_lwc_table);
  2220.                 if (named_ancestor_node(ctx, node, &qs,
  2221.                                         (void *)&tablenode) != CSS_OK) {
  2222.                         /* Didn't find, or had error */
  2223.                         lwc_string_unref(qs.name);
  2224.                         dom_string_unref(name);
  2225.                         return CSS_PROPERTY_NOT_SET;
  2226.                 }
  2227.                
  2228.                 lwc_string_unref(qs.name);
  2229.                 if (tablenode != NULL) {
  2230.                         exc = dom_element_get_attribute(tablenode,
  2231.                                         corestring_dom_border, &width);
  2232.                         if (exc != DOM_NO_ERR) {
  2233.                                 dom_string_unref(name);
  2234.                                 return CSS_BADPARM;
  2235.                         }
  2236.                 }
  2237.                 /* No need to unref tablenode, named_ancestor_node does not
  2238.                  * return a reffed node to the CSS
  2239.                  */
  2240.                 is_table_cell = true;
  2241.         } else if (dom_string_caseless_lwc_isequal(name,
  2242.                                 corestring_lwc_table)) {
  2243.                 exc = dom_element_get_attribute(node, corestring_dom_border,
  2244.                                 &width);
  2245.                 if (exc != DOM_NO_ERR) {
  2246.                         dom_string_unref(name);
  2247.                         return CSS_BADPARM;
  2248.                 }
  2249.         }
  2250.        
  2251.         dom_string_unref(name);
  2252.        
  2253.         if (width == NULL)
  2254.                 return CSS_PROPERTY_NOT_SET;
  2255.  
  2256.         if (parse_dimension(dom_string_data(width), false,
  2257.                             &hint->data.length.value,
  2258.                             &hint->data.length.unit)) {
  2259.                 if (is_table_cell &&
  2260.                     INTTOFIX(0) !=
  2261.                     hint->data.length.value) {
  2262.                         hint->data.length.value = INTTOFIX(1);
  2263.                         hint->data.length.unit = CSS_UNIT_PX;
  2264.                 }
  2265.                 hint->status = CSS_BORDER_WIDTH_WIDTH;
  2266.         } else {
  2267.                 dom_string_unref(width);
  2268.                 return CSS_PROPERTY_NOT_SET;
  2269.         }
  2270.  
  2271.         dom_string_unref(width);
  2272.  
  2273.         return CSS_OK;
  2274. }
  2275.  
  2276. static css_error
  2277. node_presentational_hint_border_trbl_style(nscss_select_ctx *ctx,
  2278.                                           dom_node *node,
  2279.                                           css_hint *hint)
  2280. {
  2281.         dom_string *name;
  2282.         dom_exception exc;
  2283.  
  2284.         exc = dom_node_get_node_name(node, &name);
  2285.         if (exc != DOM_NO_ERR)
  2286.                 return CSS_BADPARM;
  2287.  
  2288.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
  2289.             dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
  2290.                 css_qname qs;
  2291.                 dom_node *tablenode = NULL;
  2292.                 qs.ns = NULL;
  2293.                 qs.name = lwc_string_ref(corestring_lwc_table);
  2294.  
  2295.                 if (named_ancestor_node(ctx, node, &qs,
  2296.                                         (void *)&tablenode) != CSS_OK) {
  2297.                         /* Didn't find, or had error */
  2298.                         lwc_string_unref(qs.name);
  2299.                         dom_string_unref(name);
  2300.                         return CSS_PROPERTY_NOT_SET;
  2301.                 }
  2302.                
  2303.                 lwc_string_unref(qs.name);
  2304.  
  2305.                 if (tablenode != NULL) {
  2306.                         bool has_border = false;
  2307.  
  2308.                         exc = dom_element_has_attribute(tablenode,
  2309.                                                         corestring_dom_border,
  2310.                                                         &has_border);
  2311.                         if (exc != DOM_NO_ERR) {
  2312.                                 dom_string_unref(name);
  2313.                                 return CSS_BADPARM;
  2314.                         }
  2315.  
  2316.                         if (has_border) {
  2317.                                 hint->status = CSS_BORDER_STYLE_INSET;
  2318.                                 dom_string_unref(name);
  2319.                                 return CSS_OK;
  2320.                         }
  2321.                 }
  2322.                 /* No need to unref tablenode, named_ancestor_node does not
  2323.                  * return a reffed node to the CSS
  2324.                  */
  2325.         } else if (dom_string_caseless_lwc_isequal(name,
  2326.                         corestring_lwc_table)) {
  2327.                 bool has_border = false;
  2328.  
  2329.                 exc = dom_element_has_attribute(node,
  2330.                                                 corestring_dom_border,
  2331.                                                 &has_border);
  2332.                 if (exc != DOM_NO_ERR) {
  2333.                         dom_string_unref(name);
  2334.                         return CSS_BADPARM;
  2335.                 }
  2336.  
  2337.                 if (has_border) {
  2338.                         hint->status = CSS_BORDER_STYLE_OUTSET;
  2339.                         dom_string_unref(name);
  2340.                         return CSS_OK;
  2341.                 }
  2342.         }
  2343.  
  2344.         dom_string_unref(name);
  2345.  
  2346.         return CSS_PROPERTY_NOT_SET;
  2347. }
  2348.  
  2349. static css_error
  2350. node_presentational_hint_border_trbl_color(nscss_select_ctx *ctx,
  2351.                                           dom_node *node,
  2352.                                           css_hint *hint)
  2353. {
  2354.         dom_string *name;
  2355.         dom_string *bordercolor = NULL;
  2356.         dom_exception err;
  2357.  
  2358.         err = dom_node_get_node_name(node, &name);
  2359.         if (err != DOM_NO_ERR)
  2360.                 return CSS_PROPERTY_NOT_SET;
  2361.  
  2362.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
  2363.             dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
  2364.                 css_qname qs;
  2365.                 dom_node *tablenode = NULL;
  2366.                 qs.ns = NULL;
  2367.                 qs.name = lwc_string_ref(corestring_lwc_table);
  2368.  
  2369.                 if (named_ancestor_node(ctx, node, &qs,
  2370.                                         (void *)&tablenode) != CSS_OK) {
  2371.                         /* Didn't find, or had error */
  2372.                         lwc_string_unref(qs.name);
  2373.                         dom_string_unref(name);
  2374.                         return CSS_PROPERTY_NOT_SET;
  2375.                 }
  2376.                
  2377.                 lwc_string_unref(qs.name);
  2378.  
  2379.                 if (tablenode != NULL) {
  2380.                         err = dom_element_get_attribute(node,
  2381.                                         corestring_dom_bordercolor,
  2382.                                         &bordercolor);
  2383.                 }
  2384.                 /* No need to unref tablenode, named_ancestor_node does not
  2385.                  * return a reffed node to the CSS
  2386.                  */
  2387.  
  2388.         } else if (dom_string_caseless_lwc_isequal(name,
  2389.                         corestring_lwc_table)) {
  2390.                 err = dom_element_get_attribute(node,
  2391.                                 corestring_dom_bordercolor,
  2392.                                 &bordercolor);
  2393.         }
  2394.  
  2395.         dom_string_unref(name);
  2396.  
  2397.         if ((err != DOM_NO_ERR) || (bordercolor == NULL)) {
  2398.                 return CSS_PROPERTY_NOT_SET;
  2399.         }
  2400.  
  2401.         if (nscss_parse_colour((const char *)dom_string_data(bordercolor),
  2402.                                &hint->data.color)) {
  2403.                 hint->status = CSS_BORDER_COLOR_COLOR;
  2404.                 dom_string_unref(bordercolor);
  2405.                 return CSS_OK;
  2406.         }
  2407.  
  2408.         dom_string_unref(bordercolor);
  2409.         return CSS_PROPERTY_NOT_SET;
  2410. }
  2411.  
  2412. static css_error
  2413. node_presentational_hint_border_spacing(nscss_select_ctx *ctx,
  2414.                                           dom_node *node,
  2415.                                           css_hint *hint)
  2416. {
  2417.         dom_exception err;
  2418.         dom_string *node_name = NULL;
  2419.         dom_string *cellspacing = NULL;
  2420.  
  2421.         err = dom_node_get_node_name(node, &node_name);
  2422.         if ((err != DOM_NO_ERR) || (node_name == NULL)) {
  2423.                 return CSS_PROPERTY_NOT_SET;
  2424.         }
  2425.  
  2426.         if (!dom_string_caseless_lwc_isequal(node_name,
  2427.                         corestring_lwc_table)) {
  2428.                 dom_string_unref(node_name);
  2429.                 return CSS_PROPERTY_NOT_SET;
  2430.         }
  2431.  
  2432.         dom_string_unref(node_name);
  2433.  
  2434.         err = dom_element_get_attribute(node,
  2435.                         corestring_dom_cellspacing, &cellspacing);
  2436.         if ((err != DOM_NO_ERR) || (cellspacing == NULL)) {
  2437.                 return CSS_PROPERTY_NOT_SET;
  2438.         }
  2439.  
  2440.  
  2441.         if (parse_dimension((const char *)dom_string_data(cellspacing),
  2442.                             false,
  2443.                             &hint->data.position.h.value,
  2444.                             &hint->data.position.h.unit)) {
  2445.  
  2446.                 hint->data.position.v = hint->data.position.h;
  2447.                 hint->status = CSS_BORDER_SPACING_SET;
  2448.  
  2449.                 dom_string_unref(cellspacing);
  2450.                 return CSS_OK;
  2451.         }
  2452.  
  2453.         dom_string_unref(cellspacing);
  2454.         return CSS_PROPERTY_NOT_SET;
  2455. }
  2456.  
  2457. static css_error
  2458. node_presentational_hint_width(nscss_select_ctx *ctx,
  2459.                                           dom_node *node,
  2460.                                           css_hint *hint)
  2461. {
  2462.         dom_string *name;
  2463.         dom_string *width = NULL;
  2464.         dom_exception err;
  2465.         bool textarea = false;
  2466.         bool input = false;
  2467.  
  2468.         err = dom_node_get_node_name(node, &name);
  2469.         if (err != DOM_NO_ERR)
  2470.                 return CSS_PROPERTY_NOT_SET;
  2471.  
  2472.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_hr) ||
  2473.             dom_string_caseless_lwc_isequal(name, corestring_lwc_iframe) ||
  2474.             dom_string_caseless_lwc_isequal(name, corestring_lwc_img) ||
  2475.             dom_string_caseless_lwc_isequal(name, corestring_lwc_object) ||
  2476.             dom_string_caseless_lwc_isequal(name, corestring_lwc_table) ||
  2477.             dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
  2478.             dom_string_caseless_lwc_isequal(name, corestring_lwc_th) ||
  2479.             dom_string_caseless_lwc_isequal(name, corestring_lwc_applet)) {
  2480.                 err = dom_element_get_attribute(node,
  2481.                                 corestring_dom_width, &width);
  2482.         } else if (dom_string_caseless_lwc_isequal(name,
  2483.                                 corestring_lwc_textarea)) {
  2484.                 textarea = true;
  2485.                 err = dom_element_get_attribute(node,
  2486.                                 corestring_dom_cols, &width);
  2487.         } else if (dom_string_caseless_lwc_isequal(name,
  2488.                         corestring_lwc_input)) {
  2489.                 input = true;
  2490.                 err = dom_element_get_attribute(node,
  2491.                                 corestring_dom_size, &width);
  2492.         }
  2493.  
  2494.         dom_string_unref(name);
  2495.  
  2496.         if ((err != DOM_NO_ERR) || (width == NULL)) {
  2497.                 return CSS_PROPERTY_NOT_SET;
  2498.         }
  2499.  
  2500.         if (parse_dimension((const char *)dom_string_data(width),
  2501.                             false,
  2502.                             &hint->data.length.value,
  2503.                             &hint->data.length.unit)) {
  2504.                 hint->status = CSS_WIDTH_SET;
  2505.                 dom_string_unref(width);
  2506.  
  2507.                 if (textarea) {
  2508.                         hint->data.length.unit = CSS_UNIT_EX;
  2509.                 }
  2510.  
  2511.                 if (input) {
  2512.                         err = dom_element_get_attribute(node,
  2513.                                         corestring_dom_type, &width);
  2514.                         if (err != DOM_NO_ERR) {
  2515.                                 return CSS_PROPERTY_NOT_SET;
  2516.                         }
  2517.  
  2518.                         if ((width == NULL) ||
  2519.                             dom_string_caseless_lwc_isequal(width,
  2520.                                         corestring_lwc_text) ||
  2521.                             dom_string_caseless_lwc_isequal(width,
  2522.                                         corestring_lwc_search) ||
  2523.                             dom_string_caseless_lwc_isequal(width,
  2524.                                         corestring_lwc_file) ||
  2525.                             dom_string_caseless_lwc_isequal(width,
  2526.                                         corestring_lwc_password)) {
  2527.                                 hint->data.length.unit = CSS_UNIT_EX;
  2528.                         }
  2529.                         dom_string_unref(width);
  2530.                 }
  2531.  
  2532.                 return CSS_OK;
  2533.         }
  2534.  
  2535.         dom_string_unref(width);
  2536.         return CSS_PROPERTY_NOT_SET;
  2537.  
  2538. }
  2539.  
  2540. static css_error
  2541. node_presentational_hint_height(nscss_select_ctx *ctx,
  2542.                                           dom_node *node,
  2543.                                           css_hint *hint)
  2544. {
  2545.         dom_string *name;
  2546.         dom_string *height = NULL;
  2547.         dom_exception err;
  2548.         bool textarea = false;
  2549.  
  2550.         err = dom_node_get_node_name(node, &name);
  2551.         if (err != DOM_NO_ERR)
  2552.                 return CSS_PROPERTY_NOT_SET;
  2553.  
  2554.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_iframe) ||
  2555.             dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
  2556.             dom_string_caseless_lwc_isequal(name, corestring_lwc_th) ||
  2557.             dom_string_caseless_lwc_isequal(name, corestring_lwc_tr) ||
  2558.             dom_string_caseless_lwc_isequal(name, corestring_lwc_img) ||
  2559.             dom_string_caseless_lwc_isequal(name, corestring_lwc_object) ||
  2560.             dom_string_caseless_lwc_isequal(name, corestring_lwc_applet)) {
  2561.                 err = dom_element_get_attribute(node,
  2562.                                 corestring_dom_height, &height);
  2563.         } else if (dom_string_caseless_lwc_isequal(name,
  2564.                         corestring_lwc_textarea)) {
  2565.                 textarea = true;
  2566.                 err = dom_element_get_attribute(node,
  2567.                                 corestring_dom_rows, &height);
  2568.         }
  2569.  
  2570.         dom_string_unref(name);
  2571.  
  2572.         if ((err != DOM_NO_ERR) || (height == NULL)) {
  2573.                 return CSS_PROPERTY_NOT_SET;
  2574.         }
  2575.  
  2576.         if (parse_dimension((const char *)dom_string_data(height),
  2577.                             false,
  2578.                             &hint->data.length.value,
  2579.                             &hint->data.length.unit)) {
  2580.                 hint->status = CSS_HEIGHT_SET;
  2581.  
  2582.                 if (textarea) {
  2583.                         hint->data.length.unit = CSS_UNIT_EM;
  2584.                 }
  2585.  
  2586.                 dom_string_unref(height);
  2587.                 return CSS_OK;
  2588.         }
  2589.  
  2590.         dom_string_unref(height);
  2591.         return CSS_PROPERTY_NOT_SET;
  2592. }
  2593.  
  2594. static css_error
  2595. node_presentational_hint_font_size(nscss_select_ctx *ctx,
  2596.                                           dom_node *node,
  2597.                                           css_hint *hint)
  2598. {
  2599.         dom_exception err;
  2600.         dom_string *node_name = NULL;
  2601.         dom_string *size;
  2602.  
  2603.         err = dom_node_get_node_name(node, &node_name);
  2604.         if ((err != DOM_NO_ERR) || (node_name == NULL)) {
  2605.                 return CSS_NOMEM;
  2606.         }
  2607.  
  2608.         if (!dom_string_caseless_lwc_isequal(node_name,
  2609.                         corestring_lwc_font)) {
  2610.                 dom_string_unref(node_name);
  2611.                 return CSS_PROPERTY_NOT_SET;
  2612.         }
  2613.  
  2614.         dom_string_unref(node_name);
  2615.  
  2616.         err = dom_element_get_attribute(node, corestring_dom_size, &size);
  2617.         if ((err != DOM_NO_ERR) || (size == NULL)) {
  2618.                 return CSS_PROPERTY_NOT_SET;
  2619.         }
  2620.  
  2621.         if (parse_font_size((const char *)dom_string_data(size),
  2622.                             &hint->status,
  2623.                             &hint->data.length.value,
  2624.                             &hint->data.length.unit)) {
  2625.                 dom_string_unref(size);
  2626.                 return CSS_OK;
  2627.         }
  2628.  
  2629.         dom_string_unref(size);
  2630.         return CSS_PROPERTY_NOT_SET;
  2631. }
  2632.  
  2633. static css_error
  2634. node_presentational_hint_float(nscss_select_ctx *ctx,
  2635.                                           dom_node *node,
  2636.                                           css_hint *hint)
  2637. {
  2638.         dom_exception err;
  2639.         dom_string *node_name = NULL;
  2640.         dom_string *align;
  2641.  
  2642.         err = dom_node_get_node_name(node, &node_name);
  2643.         if ((err != DOM_NO_ERR) || (node_name == NULL)) {
  2644.                 return CSS_NOMEM;
  2645.         }
  2646.  
  2647.         /** \todo input[type=image][align=*] - $11.3.3 */
  2648.         if (!dom_string_caseless_lwc_isequal(node_name,
  2649.                         corestring_lwc_applet) &&
  2650.             !dom_string_caseless_lwc_isequal(node_name,
  2651.                         corestring_lwc_embed) &&
  2652.             !dom_string_caseless_lwc_isequal(node_name,
  2653.                         corestring_lwc_iframe) &&
  2654.             !dom_string_caseless_lwc_isequal(node_name,
  2655.                         corestring_lwc_img) &&
  2656.             !dom_string_caseless_lwc_isequal(node_name,
  2657.                         corestring_lwc_object)) {
  2658.                 dom_string_unref(node_name);
  2659.                 return CSS_PROPERTY_NOT_SET;
  2660.         }
  2661.  
  2662.         dom_string_unref(node_name);
  2663.  
  2664.         err = dom_element_get_attribute(node, corestring_dom_align, &align);
  2665.         if ((err != DOM_NO_ERR) || (align == NULL)) {
  2666.                 return CSS_PROPERTY_NOT_SET;
  2667.         }
  2668.  
  2669.         if (dom_string_caseless_lwc_isequal(align,
  2670.                         corestring_lwc_left)) {
  2671.                 hint->status = CSS_FLOAT_LEFT;
  2672.                 dom_string_unref(align);
  2673.                 return CSS_OK;
  2674.         } else if (dom_string_caseless_lwc_isequal(align,
  2675.                         corestring_lwc_right)) {
  2676.                 hint->status = CSS_FLOAT_RIGHT;
  2677.                 dom_string_unref(align);
  2678.                 return CSS_OK;
  2679.         }
  2680.  
  2681.         dom_string_unref(align);
  2682.  
  2683.         return CSS_PROPERTY_NOT_SET;
  2684. }
  2685.  
  2686. static css_error
  2687. node_presentational_hint_color(nscss_select_ctx *ctx,
  2688.                                           dom_node *node,
  2689.                                           css_hint *hint)
  2690. {
  2691.         css_error error;
  2692.         dom_exception err;
  2693.         dom_string *node_name = NULL;
  2694.         dom_string *color;
  2695.  
  2696.         err = dom_node_get_node_name(node, &node_name);
  2697.         if ((err != DOM_NO_ERR) || (node_name == NULL)) {
  2698.                 return CSS_NOMEM;
  2699.         }
  2700.  
  2701.         if (dom_string_caseless_lwc_isequal(node_name, corestring_lwc_a)) {
  2702.                 /* find body node */
  2703.                 css_qname qs;
  2704.                 dom_node *bodynode = NULL;
  2705.                 bool is_visited;
  2706.  
  2707.                 qs.ns = NULL;
  2708.                 qs.name = lwc_string_ref(corestring_lwc_body);
  2709.                 if (named_ancestor_node(ctx, node, &qs,
  2710.                                         (void *)&bodynode) != CSS_OK) {
  2711.                         /* Didn't find, or had error */
  2712.                         lwc_string_unref(qs.name);
  2713.                         dom_string_unref(node_name);
  2714.                         return CSS_PROPERTY_NOT_SET;
  2715.                 }
  2716.                
  2717.                 lwc_string_unref(qs.name);
  2718.  
  2719.                 /* deal with missing body ancestor */
  2720.                 if (bodynode == NULL) {
  2721.                         dom_string_unref(node_name);
  2722.                         return CSS_BADPARM;
  2723.                 }
  2724.  
  2725.                 error = node_is_visited(ctx, node, &is_visited);
  2726.                 if (error != CSS_OK)
  2727.                         return error;
  2728.  
  2729.                 if (is_visited) {
  2730.                         err = dom_element_get_attribute(bodynode,
  2731.                                         corestring_dom_vlink, &color);
  2732.                         if ((err != DOM_NO_ERR) || (color == NULL)) {
  2733.                                 dom_string_unref(node_name);
  2734.                                 return CSS_PROPERTY_NOT_SET;
  2735.                         }
  2736.                 } else {
  2737.                         err = dom_element_get_attribute(bodynode,
  2738.                                         corestring_dom_link, &color);
  2739.                         if ((err != DOM_NO_ERR) || (color == NULL)) {
  2740.                                 dom_string_unref(node_name);
  2741.                                 return CSS_PROPERTY_NOT_SET;
  2742.                         }
  2743.                 }
  2744.         } else if (dom_string_caseless_lwc_isequal(node_name,
  2745.                         corestring_lwc_body)) {
  2746.                 err = dom_element_get_attribute(node,
  2747.                                 corestring_dom_text, &color);
  2748.                 if ((err != DOM_NO_ERR) || (color == NULL)) {
  2749.                         dom_string_unref(node_name);
  2750.                         return CSS_PROPERTY_NOT_SET;
  2751.                 }
  2752.         } else {
  2753.                 err = dom_element_get_attribute(node,
  2754.                                 corestring_dom_color, &color);
  2755.                 if ((err != DOM_NO_ERR) || (color == NULL)) {
  2756.                         dom_string_unref(node_name);
  2757.                         return CSS_PROPERTY_NOT_SET;
  2758.                 }
  2759.         }
  2760.  
  2761.         if (!nscss_parse_colour((const char *)dom_string_data(color),
  2762.                                 &hint->data.color)) {
  2763.                 dom_string_unref(node_name);
  2764.                 return CSS_PROPERTY_NOT_SET;
  2765.         }
  2766.  
  2767.         hint->status = CSS_COLOR_COLOR;
  2768.  
  2769.         dom_string_unref(node_name);
  2770.  
  2771.         return CSS_OK;
  2772. }
  2773.  
  2774. static css_error
  2775. node_presentational_hint_caption_side(nscss_select_ctx *ctx,
  2776.                                           dom_node *node,
  2777.                                           css_hint *hint)
  2778. {
  2779.         dom_exception err;
  2780.         dom_string *node_name = NULL;
  2781.         dom_string *align = NULL;
  2782.  
  2783.         err = dom_node_get_node_name(node, &node_name);
  2784.         if ((err != DOM_NO_ERR) || (node_name == NULL)) {
  2785.                 return CSS_PROPERTY_NOT_SET;
  2786.         }
  2787.  
  2788.         if (!dom_string_caseless_lwc_isequal(node_name,
  2789.                         corestring_lwc_caption)) {
  2790.                 dom_string_unref(node_name);
  2791.                 return CSS_PROPERTY_NOT_SET;
  2792.         }
  2793.  
  2794.         dom_string_unref(node_name);
  2795.  
  2796.         err = dom_element_get_attribute(node, corestring_dom_align, &align);
  2797.         if ((err != DOM_NO_ERR) || (align == NULL)) {
  2798.                 return CSS_PROPERTY_NOT_SET;
  2799.         }
  2800.  
  2801.         if (dom_string_caseless_lwc_isequal(align, corestring_lwc_bottom)) {
  2802.                 hint->status = CSS_CAPTION_SIDE_BOTTOM;
  2803.                 dom_string_unref(align);
  2804.                 return CSS_OK;
  2805.         }
  2806.  
  2807.         dom_string_unref(align);
  2808.  
  2809.         return CSS_PROPERTY_NOT_SET;
  2810. }
  2811.  
  2812. static css_error
  2813. node_presentational_hint_background_color(nscss_select_ctx *ctx,
  2814.                                           dom_node *node,
  2815.                                           css_hint *hint)
  2816. {
  2817.         dom_exception err;
  2818.         dom_string *bgcolor;
  2819.  
  2820.         err = dom_element_get_attribute(node,
  2821.                         corestring_dom_bgcolor, &bgcolor);
  2822.         if ((err != DOM_NO_ERR) || (bgcolor == NULL)) {
  2823.                 return CSS_PROPERTY_NOT_SET;
  2824.         }
  2825.  
  2826.         if (nscss_parse_colour((const char *)dom_string_data(bgcolor),
  2827.                                &hint->data.color)) {
  2828.                 hint->status = CSS_BACKGROUND_COLOR_COLOR;
  2829.                 dom_string_unref(bgcolor);
  2830.                 return CSS_OK;
  2831.         }
  2832.  
  2833.         dom_string_unref(bgcolor);
  2834.  
  2835.         return CSS_PROPERTY_NOT_SET;
  2836. }
  2837.  
  2838. static css_error
  2839. node_presentational_hint_background_image(nscss_select_ctx *ctx,
  2840.                                           dom_node *node,
  2841.                                           css_hint *hint)
  2842. {
  2843.         dom_exception err;
  2844.         dom_string *atr_val;
  2845.         nserror error;
  2846.         nsurl *url;
  2847.         lwc_string *iurl;
  2848.         lwc_error lerror;
  2849.  
  2850.         err = dom_element_get_attribute(node,
  2851.                         corestring_dom_background, &atr_val);
  2852.         if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
  2853.                 return CSS_PROPERTY_NOT_SET;
  2854.         }
  2855.  
  2856.         error = nsurl_join(ctx->base_url,
  2857.                         (const char *)dom_string_data(atr_val), &url);
  2858.  
  2859.         dom_string_unref(atr_val);
  2860.  
  2861.         if (error != NSERROR_OK) {
  2862.                 return CSS_NOMEM;
  2863.         }
  2864.  
  2865.         lerror = lwc_intern_string(nsurl_access(url),
  2866.                         nsurl_length(url), &iurl);
  2867.  
  2868.         nsurl_unref(url);
  2869.  
  2870.         if (lerror == lwc_error_oom) {
  2871.                 return CSS_NOMEM;
  2872.         }
  2873.  
  2874.         if (lerror == lwc_error_ok) {
  2875.                 hint->data.string = iurl;
  2876.                 hint->status = CSS_BACKGROUND_IMAGE_IMAGE;
  2877.                 return CSS_OK;
  2878.         }
  2879.        
  2880.         return CSS_PROPERTY_NOT_SET;
  2881. }
  2882.  
  2883. /**
  2884.  * Callback to retrieve presentational hints for a node
  2885.  *
  2886.  * \param pw        HTML document
  2887.  * \param node      DOM node
  2888.  * \param property  CSS property to retrieve
  2889.  * \param hint      Pointer to hint object to populate
  2890.  * \return CSS_OK               on success,
  2891.  *         CSS_PROPERTY_NOT_SET if there is no hint for the requested property,
  2892.  *         CSS_NOMEM            on memory exhaustion.
  2893.  */
  2894. css_error node_presentational_hint(void *pw, void *node,
  2895.                 uint32_t property, css_hint *hint)
  2896. {
  2897.  
  2898.         switch (property) {
  2899.         case CSS_PROP_BACKGROUND_IMAGE:
  2900.                 return node_presentational_hint_background_image(pw, node, hint);
  2901.  
  2902.         case CSS_PROP_BACKGROUND_COLOR:
  2903.                 return node_presentational_hint_background_color(pw, node, hint);
  2904.         case CSS_PROP_CAPTION_SIDE:
  2905.                 return node_presentational_hint_caption_side(pw, node, hint);
  2906.  
  2907.         case CSS_PROP_COLOR:
  2908.                 return node_presentational_hint_color(pw, node, hint);
  2909.  
  2910.         case CSS_PROP_FLOAT:
  2911.                 return node_presentational_hint_float(pw, node, hint);
  2912.  
  2913.         case CSS_PROP_FONT_SIZE:
  2914.                 return node_presentational_hint_font_size(pw, node, hint);
  2915.  
  2916.         case CSS_PROP_HEIGHT:
  2917.                 return node_presentational_hint_height(pw, node, hint);
  2918.  
  2919.         case CSS_PROP_WIDTH:
  2920.                 return node_presentational_hint_width(pw, node, hint);
  2921.  
  2922.         case CSS_PROP_BORDER_SPACING:
  2923.                 return node_presentational_hint_border_spacing(pw, node, hint);
  2924.  
  2925.         case CSS_PROP_BORDER_TOP_COLOR :
  2926.         case CSS_PROP_BORDER_RIGHT_COLOR :
  2927.         case CSS_PROP_BORDER_BOTTOM_COLOR :
  2928.         case CSS_PROP_BORDER_LEFT_COLOR :
  2929.                 return node_presentational_hint_border_trbl_color(pw, node, hint);
  2930.  
  2931.         case CSS_PROP_BORDER_TOP_STYLE :
  2932.         case CSS_PROP_BORDER_RIGHT_STYLE :
  2933.         case CSS_PROP_BORDER_BOTTOM_STYLE :
  2934.         case CSS_PROP_BORDER_LEFT_STYLE :
  2935.                 return node_presentational_hint_border_trbl_style(pw, node, hint);
  2936.  
  2937.         case CSS_PROP_BORDER_TOP_WIDTH :
  2938.         case CSS_PROP_BORDER_RIGHT_WIDTH :
  2939.         case CSS_PROP_BORDER_BOTTOM_WIDTH :
  2940.         case CSS_PROP_BORDER_LEFT_WIDTH :
  2941.                 return node_presentational_hint_border_trbl_width(pw, node, hint);
  2942.  
  2943.         case CSS_PROP_MARGIN_TOP :
  2944.         case CSS_PROP_MARGIN_BOTTOM :
  2945.                 return node_presentational_hint_margin_tb(pw, node, hint);
  2946.  
  2947.         case CSS_PROP_MARGIN_RIGHT:
  2948.         case CSS_PROP_MARGIN_LEFT:
  2949.                 return node_presentational_hint_margin_rl(pw, node, hint, property);
  2950.  
  2951.         case CSS_PROP_PADDING_TOP:
  2952.         case CSS_PROP_PADDING_RIGHT :
  2953.         case CSS_PROP_PADDING_BOTTOM :
  2954.         case CSS_PROP_PADDING_LEFT:
  2955.                 return node_presentational_hint_padding_trbl(pw, node, hint);
  2956.  
  2957.         case CSS_PROP_TEXT_ALIGN:
  2958.                 return node_presentational_hint_text_align(pw, node, hint);
  2959.  
  2960.         case CSS_PROP_VERTICAL_ALIGN:
  2961.                 return node_presentational_hint_vertical_align(pw, node, hint);
  2962.         }
  2963.  
  2964.         return CSS_PROPERTY_NOT_SET;
  2965. }
  2966.  
  2967. /**
  2968.  * Callback to retrieve the User-Agent defaults for a CSS property.
  2969.  *
  2970.  * \param pw        HTML document
  2971.  * \param property  Property to retrieve defaults for
  2972.  * \param hint      Pointer to hint object to populate
  2973.  * \return CSS_OK       on success,
  2974.  *         CSS_INVALID  if the property should not have a user-agent default.
  2975.  */
  2976. css_error ua_default_for_property(void *pw, uint32_t property, css_hint *hint)
  2977. {
  2978.         if (property == CSS_PROP_COLOR) {
  2979.                 hint->data.color = 0xff000000;
  2980.                 hint->status = CSS_COLOR_COLOR;
  2981.         } else if (property == CSS_PROP_FONT_FAMILY) {
  2982.                 hint->data.strings = NULL;
  2983.                 switch (nsoption_int(font_default)) {
  2984.                 case PLOT_FONT_FAMILY_SANS_SERIF:
  2985.                         hint->status = CSS_FONT_FAMILY_SANS_SERIF;
  2986.                         break;
  2987.                 case PLOT_FONT_FAMILY_SERIF:
  2988.                         hint->status = CSS_FONT_FAMILY_SERIF;
  2989.                         break;
  2990.                 case PLOT_FONT_FAMILY_MONOSPACE:
  2991.                         hint->status = CSS_FONT_FAMILY_MONOSPACE;
  2992.                         break;
  2993.                 case PLOT_FONT_FAMILY_CURSIVE:
  2994.                         hint->status = CSS_FONT_FAMILY_CURSIVE;
  2995.                         break;
  2996.                 case PLOT_FONT_FAMILY_FANTASY:
  2997.                         hint->status = CSS_FONT_FAMILY_FANTASY;
  2998.                         break;
  2999.                 }
  3000.         } else if (property == CSS_PROP_QUOTES) {
  3001.                 /** \todo Not exactly useful :) */
  3002.                 hint->data.strings = NULL;
  3003.                 hint->status = CSS_QUOTES_NONE;
  3004.         } else if (property == CSS_PROP_VOICE_FAMILY) {
  3005.                 /** \todo Fix this when we have voice-family done */
  3006.                 hint->data.strings = NULL;
  3007.                 hint->status = 0;
  3008.         } else {
  3009.                 return CSS_INVALID;
  3010.         }
  3011.  
  3012.         return CSS_OK;
  3013. }
  3014.  
  3015. /**
  3016.  * Mapping of colour name to CSS color
  3017.  */
  3018. struct colour_map {
  3019.         const char *name;
  3020.         css_color color;
  3021. };
  3022.  
  3023. /**
  3024.  * Name comparator for named colour matching
  3025.  *
  3026.  * \param a  Name to match
  3027.  * \param b  Colour map entry to consider
  3028.  * \return 0   on match,
  3029.  *         < 0 if a < b,
  3030.  *         > 0 if b > a.
  3031.  */
  3032. int cmp_colour_name(const void *a, const void *b)
  3033. {
  3034.         const char *aa = a;
  3035.         const struct colour_map *bb = b;
  3036.  
  3037.         return strcasecmp(aa, bb->name);
  3038. }
  3039.  
  3040. /**
  3041.  * Parse a named colour
  3042.  *
  3043.  * \param name    Name to parse
  3044.  * \param result  Pointer to location to receive css_color
  3045.  * \return true on success, false on invalid input
  3046.  */
  3047. bool parse_named_colour(const char *name, css_color *result)
  3048. {
  3049.         static const struct colour_map named_colours[] = {
  3050.                 { "aliceblue",          0xfff0f8ff },
  3051.                 { "antiquewhite",       0xfffaebd7 },
  3052.                 { "aqua",               0xff00ffff },
  3053.                 { "aquamarine",         0xff7fffd4 },
  3054.                 { "azure",              0xfff0ffff },
  3055.                 { "beige",              0xfff5f5dc },
  3056.                 { "bisque",             0xffffe4c4 },
  3057.                 { "black",              0xff000000 },
  3058.                 { "blanchedalmond",     0xffffebcd },
  3059.                 { "blue",               0xff0000ff },
  3060.                 { "blueviolet",         0xff8a2be2 },
  3061.                 { "brown",              0xffa52a2a },
  3062.                 { "burlywood",          0xffdeb887 },
  3063.                 { "cadetblue",          0xff5f9ea0 },
  3064.                 { "chartreuse",         0xff7fff00 },
  3065.                 { "chocolate",          0xffd2691e },
  3066.                 { "coral",              0xffff7f50 },
  3067.                 { "cornflowerblue",     0xff6495ed },
  3068.                 { "cornsilk",           0xfffff8dc },
  3069.                 { "crimson",            0xffdc143c },
  3070.                 { "cyan",               0xff00ffff },
  3071.                 { "darkblue",           0xff00008b },
  3072.                 { "darkcyan",           0xff008b8b },
  3073.                 { "darkgoldenrod",      0xffb8860b },
  3074.                 { "darkgray",           0xffa9a9a9 },
  3075.                 { "darkgreen",          0xff006400 },
  3076.                 { "darkgrey",           0xffa9a9a9 },
  3077.                 { "darkkhaki",          0xffbdb76b },
  3078.                 { "darkmagenta",        0xff8b008b },
  3079.                 { "darkolivegreen",     0xff556b2f },
  3080.                 { "darkorange",         0xffff8c00 },
  3081.                 { "darkorchid",         0xff9932cc },
  3082.                 { "darkred",            0xff8b0000 },
  3083.                 { "darksalmon",         0xffe9967a },
  3084.                 { "darkseagreen",       0xff8fbc8f },
  3085.                 { "darkslateblue",      0xff483d8b },
  3086.                 { "darkslategray",      0xff2f4f4f },
  3087.                 { "darkslategrey",      0xff2f4f4f },
  3088.                 { "darkturquoise",      0xff00ced1 },
  3089.                 { "darkviolet",         0xff9400d3 },
  3090.                 { "deeppink",           0xffff1493 },
  3091.                 { "deepskyblue",        0xff00bfff },
  3092.                 { "dimgray",            0xff696969 },
  3093.                 { "dimgrey",            0xff696969 },
  3094.                 { "dodgerblue",         0xff1e90ff },
  3095.                 { "feldspar",           0xffd19275 },
  3096.                 { "firebrick",          0xffb22222 },
  3097.                 { "floralwhite",        0xfffffaf0 },
  3098.                 { "forestgreen",        0xff228b22 },
  3099.                 { "fuchsia",            0xffff00ff },
  3100.                 { "gainsboro",          0xffdcdcdc },
  3101.                 { "ghostwhite",         0xfff8f8ff },
  3102.                 { "gold",               0xffffd700 },
  3103.                 { "goldenrod",          0xffdaa520 },
  3104.                 { "gray",               0xff808080 },
  3105.                 { "green",              0xff008000 },
  3106.                 { "greenyellow",        0xffadff2f },
  3107.                 { "grey",               0xff808080 },
  3108.                 { "honeydew",           0xfff0fff0 },
  3109.                 { "hotpink",            0xffff69b4 },
  3110.                 { "indianred",          0xffcd5c5c },
  3111.                 { "indigo",             0xff4b0082 },
  3112.                 { "ivory",              0xfffffff0 },
  3113.                 { "khaki",              0xfff0e68c },
  3114.                 { "lavender",           0xffe6e6fa },
  3115.                 { "lavenderblush",      0xfffff0f5 },
  3116.                 { "lawngreen",          0xff7cfc00 },
  3117.                 { "lemonchiffon",       0xfffffacd },
  3118.                 { "lightblue",          0xffadd8e6 },
  3119.                 { "lightcoral",         0xfff08080 },
  3120.                 { "lightcyan",          0xffe0ffff },
  3121.                 { "lightgoldenrodyellow",       0xfffafad2 },
  3122.                 { "lightgray",          0xffd3d3d3 },
  3123.                 { "lightgreen",         0xff90ee90 },
  3124.                 { "lightgrey",          0xffd3d3d3 },
  3125.                 { "lightpink",          0xffffb6c1 },
  3126.                 { "lightsalmon",        0xffffa07a },
  3127.                 { "lightseagreen",      0xff20b2aa },
  3128.                 { "lightskyblue",       0xff87cefa },
  3129.                 { "lightslateblue",     0xff8470ff },
  3130.                 { "lightslategray",     0xff778899 },
  3131.                 { "lightslategrey",     0xff778899 },
  3132.                 { "lightsteelblue",     0xffb0c4de },
  3133.                 { "lightyellow",        0xffffffe0 },
  3134.                 { "lime",               0xff00ff00 },
  3135.                 { "limegreen",          0xff32cd32 },
  3136.                 { "linen",              0xfffaf0e6 },
  3137.                 { "magenta",            0xffff00ff },
  3138.                 { "maroon",             0xff800000 },
  3139.                 { "mediumaquamarine",   0xff66cdaa },
  3140.                 { "mediumblue",         0xff0000cd },
  3141.                 { "mediumorchid",       0xffba55d3 },
  3142.                 { "mediumpurple",       0xff9370db },
  3143.                 { "mediumseagreen",     0xff3cb371 },
  3144.                 { "mediumslateblue",    0xff7b68ee },
  3145.                 { "mediumspringgreen",  0xff00fa9a },
  3146.                 { "mediumturquoise",    0xff48d1cc },
  3147.                 { "mediumvioletred",    0xffc71585 },
  3148.                 { "midnightblue",       0xff191970 },
  3149.                 { "mintcream",          0xfff5fffa },
  3150.                 { "mistyrose",          0xffffe4e1 },
  3151.                 { "moccasin",           0xffffe4b5 },
  3152.                 { "navajowhite",        0xffffdead },
  3153.                 { "navy",               0xff000080 },
  3154.                 { "oldlace",            0xfffdf5e6 },
  3155.                 { "olive",              0xff808000 },
  3156.                 { "olivedrab",          0xff6b8e23 },
  3157.                 { "orange",             0xffffa500 },
  3158.                 { "orangered",          0xffff4500 },
  3159.                 { "orchid",             0xffda70d6 },
  3160.                 { "palegoldenrod",      0xffeee8aa },
  3161.                 { "palegreen",          0xff98fb98 },
  3162.                 { "paleturquoise",      0xffafeeee },
  3163.                 { "palevioletred",      0xffdb7093 },
  3164.                 { "papayawhip",         0xffffefd5 },
  3165.                 { "peachpuff",          0xffffdab9 },
  3166.                 { "peru",               0xffcd853f },
  3167.                 { "pink",               0xffffc0cb },
  3168.                 { "plum",               0xffdda0dd },
  3169.                 { "powderblue",         0xffb0e0e6 },
  3170.                 { "purple",             0xff800080 },
  3171.                 { "red",                0xffff0000 },
  3172.                 { "rosybrown",          0xffbc8f8f },
  3173.                 { "royalblue",          0xff4169e1 },
  3174.                 { "saddlebrown",        0xff8b4513 },
  3175.                 { "salmon",             0xfffa8072 },
  3176.                 { "sandybrown",         0xfff4a460 },
  3177.                 { "seagreen",           0xff2e8b57 },
  3178.                 { "seashell",           0xfffff5ee },
  3179.                 { "sienna",             0xffa0522d },
  3180.                 { "silver",             0xffc0c0c0 },
  3181.                 { "skyblue",            0xff87ceeb },
  3182.                 { "slateblue",          0xff6a5acd },
  3183.                 { "slategray",          0xff708090 },
  3184.                 { "slategrey",          0xff708090 },
  3185.                 { "snow",               0xfffffafa },
  3186.                 { "springgreen",        0xff00ff7f },
  3187.                 { "steelblue",          0xff4682b4 },
  3188.                 { "tan",                0xffd2b48c },
  3189.                 { "teal",               0xff008080 },
  3190.                 { "thistle",            0xffd8bfd8 },
  3191.                 { "tomato",             0xffff6347 },
  3192.                 { "turquoise",          0xff40e0d0 },
  3193.                 { "violet",             0xffee82ee },
  3194.                 { "violetred",          0xffd02090 },
  3195.                 { "wheat",              0xfff5deb3 },
  3196.                 { "white",              0xffffffff },
  3197.                 { "whitesmoke",         0xfff5f5f5 },
  3198.                 { "yellow",             0xffffff00 },
  3199.                 { "yellowgreen",        0xff9acd32 }
  3200.         };
  3201.         const struct colour_map *entry;
  3202.  
  3203.         entry = bsearch(name, named_colours,
  3204.                         sizeof(named_colours) / sizeof(named_colours[0]),
  3205.                         sizeof(named_colours[0]),
  3206.                         cmp_colour_name);
  3207.  
  3208.         if (entry != NULL)
  3209.                 *result = entry->color;
  3210.  
  3211.         return entry != NULL;
  3212. }
  3213.  
  3214. /**
  3215.  * Parse a dimension string
  3216.  *
  3217.  * \param data    Data to parse (NUL-terminated)
  3218.  * \param strict  Whether to enforce strict parsing rules
  3219.  * \param length  Pointer to location to receive dimension's length
  3220.  * \param unit    Pointer to location to receive dimension's unit
  3221.  * \return true on success, false on invalid input
  3222.  */
  3223. bool parse_dimension(const char *data, bool strict, css_fixed *length,
  3224.                 css_unit *unit)
  3225. {
  3226.         size_t len;
  3227.         size_t read;
  3228.         css_fixed value;
  3229.  
  3230.         len = strlen(data);
  3231.  
  3232.         if (parse_number(data, false, true, &value, &read) == false)
  3233.                 return false;
  3234.  
  3235.         if (strict && value < INTTOFIX(1))
  3236.                 return false;
  3237.  
  3238.         *length = value;
  3239.  
  3240.         if (len > read && data[read] == '%')
  3241.                 *unit = CSS_UNIT_PCT;
  3242.         else
  3243.                 *unit = CSS_UNIT_PX;
  3244.  
  3245.         return true;
  3246. }
  3247.  
  3248. /**
  3249.  * Parse a number string
  3250.  *
  3251.  * \param data  Data to parse (NUL-terminated)
  3252.  * \param maybe_negative  Negative numbers permitted
  3253.  * \param real            Floating point numbers permitted
  3254.  * \param value           Pointer to location to receive numeric value
  3255.  * \param consumed        Pointer to location to receive number of input
  3256.  *                        bytes consumed
  3257.  * \return true on success, false on invalid input
  3258.  */
  3259. bool parse_number(const char *data, bool maybe_negative, bool real,
  3260.                 css_fixed *value, size_t *consumed)
  3261. {
  3262.         size_t len;
  3263.         const uint8_t *ptr;
  3264.         int32_t intpart = 0;
  3265.         int32_t fracpart = 0;
  3266.         int32_t pwr = 1;
  3267.         int sign = 1;
  3268.  
  3269.         *consumed = 0;
  3270.  
  3271.         len = strlen(data);
  3272.         ptr = (const uint8_t *) data;
  3273.  
  3274.         if (len == 0)
  3275.                 return false;
  3276.  
  3277.         /* Skip leading whitespace */
  3278.         while (len > 0 && isWhitespace(ptr[0])) {
  3279.                 len--;
  3280.                 ptr++;
  3281.         }
  3282.  
  3283.         if (len == 0)
  3284.                 return false;
  3285.  
  3286.         /* Extract sign, if any */
  3287.         if (ptr[0] == '+') {
  3288.                 len--;
  3289.                 ptr++;
  3290.         } else if (ptr[0] == '-' && maybe_negative) {
  3291.                 sign = -1;
  3292.                 len--;
  3293.                 ptr++;
  3294.         }
  3295.  
  3296.         if (len == 0)
  3297.                 return false;
  3298.  
  3299.         /* Must have a digit [0,9] */
  3300.         if ('0' > ptr[0] || ptr[0] > '9')
  3301.                 return false;
  3302.  
  3303.         /* Now extract intpart, assuming base 10 */
  3304.         while (len > 0) {
  3305.                 /* Stop on first non-digit */
  3306.                 if (ptr[0] < '0' || '9' < ptr[0])
  3307.                         break;
  3308.  
  3309.                 /* Prevent overflow of 'intpart'; proper clamping below */
  3310.                 if (intpart < (1 << 22)) {
  3311.                         intpart *= 10;
  3312.                         intpart += ptr[0] - '0';
  3313.                 }
  3314.                 ptr++;
  3315.                 len--;
  3316.         }
  3317.  
  3318.         /* And fracpart, again, assuming base 10 */
  3319.         if (real && len > 1 && ptr[0] == '.' &&
  3320.                         ('0' <= ptr[1] && ptr[1] <= '9')) {
  3321.                 ptr++;
  3322.                 len--;
  3323.  
  3324.                 while (len > 0) {
  3325.                         if (ptr[0] < '0' || '9' < ptr[0])
  3326.                                 break;
  3327.  
  3328.                         if (pwr < 1000000) {
  3329.                                 pwr *= 10;
  3330.                                 fracpart *= 10;
  3331.                                 fracpart += ptr[0] - '0';
  3332.                         }
  3333.                         ptr++;
  3334.                         len--;
  3335.                 }
  3336.  
  3337.                 fracpart = ((1 << 10) * fracpart + pwr/2) / pwr;
  3338.                 if (fracpart >= (1 << 10)) {
  3339.                         intpart++;
  3340.                         fracpart &= (1 << 10) - 1;
  3341.                 }
  3342.         }
  3343.  
  3344.         if (sign > 0) {
  3345.                 /* If the result is larger than we can represent,
  3346.                  * then clamp to the maximum value we can store. */
  3347.                 if (intpart >= (1 << 21)) {
  3348.                         intpart = (1 << 21) - 1;
  3349.                         fracpart = (1 << 10) - 1;
  3350.                 }
  3351.         } else {
  3352.                 /* If the negated result is smaller than we can represent
  3353.                  * then clamp to the minimum value we can store. */
  3354.                 if (intpart >= (1 << 21)) {
  3355.                         intpart = -(1 << 21);
  3356.                         fracpart = 0;
  3357.                 } else {
  3358.                         intpart = -intpart;
  3359.                         if (fracpart) {
  3360.                                 fracpart = (1 << 10) - fracpart;
  3361.                                 intpart--;
  3362.                         }
  3363.                 }
  3364.         }
  3365.  
  3366.         *value = (intpart << 10) | fracpart;
  3367.  
  3368.         *consumed = ptr - (const uint8_t *) data;
  3369.  
  3370.         return true;
  3371. }
  3372.  
  3373. /**
  3374.  * Parse a font \@size attribute
  3375.  *
  3376.  * \param size  Data to parse (NUL-terminated)
  3377.  * \param val   Pointer to location to receive enum value
  3378.  * \param len   Pointer to location to receive length
  3379.  * \param unit  Pointer to location to receive unit
  3380.  * \return True on success, false on failure
  3381.  */
  3382. bool parse_font_size(const char *size, uint8_t *val,
  3383.                 css_fixed *len, css_unit *unit)
  3384. {
  3385.         static const uint8_t size_map[] = {
  3386.                 CSS_FONT_SIZE_XX_SMALL,
  3387.                 CSS_FONT_SIZE_SMALL,
  3388.                 CSS_FONT_SIZE_MEDIUM,
  3389.                 CSS_FONT_SIZE_LARGE,
  3390.                 CSS_FONT_SIZE_X_LARGE,
  3391.                 CSS_FONT_SIZE_XX_LARGE,
  3392.                 CSS_FONT_SIZE_DIMENSION /* xxx-large (see below) */
  3393.         };
  3394.  
  3395.         const char *p = size;
  3396.         char mode;
  3397.         int value = 0;
  3398.  
  3399.         /* Skip whitespace */
  3400.         while (*p != '\0' && isWhitespace(*p))
  3401.                 p++;
  3402.  
  3403.         mode = *p;
  3404.  
  3405.         /* Skip +/- */
  3406.         if (mode == '+' || mode == '-')
  3407.                 p++;
  3408.  
  3409.         /* Need at least one digit */
  3410.         if (*p < '0' || *p > '9') {
  3411.                 return false;
  3412.         }
  3413.  
  3414.         /* Consume digits, computing value */
  3415.         while ('0' <= *p && *p <= '9') {
  3416.                 value = value * 10 + (*p - '0');
  3417.                 p++;
  3418.         }
  3419.  
  3420.         /* Resolve relative sizes */
  3421.         if (mode == '+')
  3422.                 value += 3;
  3423.         else if (mode == '-')
  3424.                 value = 3 - value;
  3425.  
  3426.         /* Clamp to range [1,7] */
  3427.         if (value < 1)
  3428.                 value = 1;
  3429.         else if (value > 7)
  3430.                 value = 7;
  3431.  
  3432.         if (value == 7) {
  3433.                 /* Manufacture xxx-large */
  3434.           *len = FDIV(FMUL(INTTOFIX(3), INTTOFIX(nsoption_int(font_size))),
  3435.                                 F_10);
  3436.         } else {
  3437.                 /* Len is irrelevant */
  3438.                 *len = 0;
  3439.         }
  3440.  
  3441.         *unit = CSS_UNIT_PT;
  3442.         *val = size_map[value - 1];
  3443.  
  3444.         return true;
  3445. }
  3446.  
  3447. /******************************************************************************
  3448.  * Utility functions                                                          *
  3449.  ******************************************************************************/
  3450.  
  3451. /**
  3452.  * Determine if a given character is whitespace
  3453.  *
  3454.  * \param c  Character to consider
  3455.  * \return true if character is whitespace, false otherwise
  3456.  */
  3457. bool isWhitespace(char c)
  3458. {
  3459.         return c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\n';
  3460. }
  3461.  
  3462. /**
  3463.  * Determine if a given character is a valid hex digit
  3464.  *
  3465.  * \param c  Character to consider
  3466.  * \return true if character is a valid hex digit, false otherwise
  3467.  */
  3468. bool isHex(char c)
  3469. {
  3470.         return ('0' <= c && c <= '9') ||
  3471.                         ('A' <= (c & ~0x20) && (c & ~0x20) <= 'F');
  3472. }
  3473.  
  3474. /**
  3475.  * Convert a character representing a hex digit to the corresponding hex value
  3476.  *
  3477.  * \param c  Character to convert
  3478.  * \return Hex value represented by character
  3479.  *
  3480.  * \note This function assumes an ASCII-compatible character set
  3481.  */
  3482. uint8_t charToHex(char c)
  3483. {
  3484.         /* 0-9 */
  3485.         c -= '0';
  3486.  
  3487.         /* A-F */
  3488.         if (c > 9)
  3489.                 c -= 'A' - '9' - 1;
  3490.  
  3491.         /* a-f */
  3492.         if (c > 15)
  3493.                 c -= 'a' - 'A';
  3494.  
  3495.         return c;
  3496. }
  3497.  
  3498.