Gitweb links:

...log 
http://git.netsurf-browser.org/libcss.git/shortlog/accad499aed29acb7bc8fb00bea3d9f2b7f43bd1
...commit 
http://git.netsurf-browser.org/libcss.git/commit/accad499aed29acb7bc8fb00bea3d9f2b7f43bd1
...tree 
http://git.netsurf-browser.org/libcss.git/tree/accad499aed29acb7bc8fb00bea3d9f2b7f43bd1

The branch, master has been updated
       via  accad499aed29acb7bc8fb00bea3d9f2b7f43bd1 (commit)
      from  55017b90bc6927bf3a4d4056c04b242e1cc6c2ac (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commitdiff 
http://git.netsurf-browser.org/libcss.git/commit/?id=accad499aed29acb7bc8fb00bea3d9f2b7f43bd1
commit accad499aed29acb7bc8fb00bea3d9f2b7f43bd1
Author: Michael Drake <[email protected]>
Commit: Michael Drake <[email protected]>

    Tests: Select test runner: Avoid forward declarations.

diff --git a/test/select.c b/test/select.c
index c104b38..5bc7856 100644
--- a/test/select.c
+++ b/test/select.c
@@ -70,1607 +70,1509 @@ typedef struct line_ctx {
        lwc_string *attr_id;
 } line_ctx;
 
+static css_error node_name(void *pw, void *n, css_qname *qname)
+{
+       node *node = n;
 
+       UNUSED(pw);
 
+       qname->name = lwc_string_ref(node->name);
 
-static bool handle_line(const char *data, size_t datalen, void *pw);
-static void css__parse_tree(line_ctx *ctx, const char *data, size_t len);
-static void css__parse_tree_data(line_ctx *ctx, const char *data, size_t len);
-static void css__parse_sheet(line_ctx *ctx, const char *data, size_t len);
-static void css__parse_media_list(const char **data, size_t *len, css_media 
*media);
-static void css__parse_pseudo_list(const char **data, size_t *len,
-               uint32_t *element);
-static void css__parse_expected(line_ctx *ctx, const char *data, size_t len);
-static void run_test(line_ctx *ctx, const char *exp, size_t explen);
-static void destroy_tree(node *root);
+       return CSS_OK;
+}
 
-static css_error node_name(void *pw, void *node,
-               css_qname *qname);
 static css_error node_classes(void *pw, void *n,
-               lwc_string ***classes, uint32_t *n_classes);
-static css_error node_id(void *pw, void *node,
-               lwc_string **id);
-static css_error named_ancestor_node(void *pw, void *node,
-               const css_qname *qname,
-               void **ancestor);
-static css_error named_parent_node(void *pw, void *node,
-               const css_qname *qname,
-               void **parent);
-static css_error named_sibling_node(void *pw, void *node,
-               const css_qname *qname,
-               void **sibling);
-static css_error named_generic_sibling_node(void *pw, void *node,
-               const css_qname *qname,
-               void **sibling);
-static css_error parent_node(void *pw, void *node, void **parent);
-static css_error sibling_node(void *pw, void *node, void **sibling);
-static css_error node_has_name(void *pw, void *node,
-               const css_qname *qname,
-               bool *match);
-static css_error node_has_class(void *pw, void *node,
-               lwc_string *name,
-               bool *match);
-static css_error node_has_id(void *pw, void *node,
-               lwc_string *name,
-               bool *match);
-static css_error node_has_attribute(void *pw, void *node,
-               const css_qname *qname,
-               bool *match);
-static css_error node_has_attribute_equal(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_dashmatch(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_includes(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_prefix(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_suffix(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_substring(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_is_root(void *pw, void *node, bool *match);
-static css_error node_count_siblings(void *pw, void *node,
-               bool same_name, bool after, int32_t *count);
-static css_error node_is_empty(void *pw, void *node, bool *match);
-static css_error node_is_link(void *pw, void *node, bool *match);
-static css_error node_is_visited(void *pw, void *node, bool *match);
-static css_error node_is_hover(void *pw, void *node, bool *match);
-static css_error node_is_active(void *pw, void *node, bool *match);
-static css_error node_is_focus(void *pw, void *node, bool *match);
-static css_error node_is_enabled(void *pw, void *node, bool *match);
-static css_error node_is_disabled(void *pw, void *node, bool *match);
-static css_error node_is_checked(void *pw, void *node, bool *match);
-static css_error node_is_target(void *pw, void *node, bool *match);
-static css_error node_is_lang(void *pw, void *node,
-               lwc_string *lang, bool *match);
-static css_error node_presentational_hint(void *pw, void *node,
-               uint32_t *nhints, css_hint **hints);
-static css_error ua_default_for_property(void *pw, uint32_t property,
-               css_hint *hints);
-static css_error set_libcss_node_data(void *pw, void *n,
-               void *libcss_node_data);
-static css_error get_libcss_node_data(void *pw, void *n,
-               void **libcss_node_data);
+               lwc_string ***classes, uint32_t *n_classes)
+{
+       unsigned int i;
+       node *node = n;
+       UNUSED(pw);
 
-static css_unit_ctx unit_ctx = {
-       .font_size_default = 16 * (1 << CSS_RADIX_POINT),
-};
+       *classes = node->classes;
+       *n_classes = node->n_classes;
 
-static css_select_handler select_handler = {
-       CSS_SELECT_HANDLER_VERSION_1,
+       for (i = 0; i < *n_classes; i++)
+               (*classes)[i] = lwc_string_ref(node->classes[i]);
 
-       node_name,
-       node_classes,
-       node_id,
-       named_ancestor_node,
-       named_parent_node,
-       named_sibling_node,
-       named_generic_sibling_node,
-       parent_node,
-       sibling_node,
-       node_has_name,
-       node_has_class,
-       node_has_id,
-       node_has_attribute,
-       node_has_attribute_equal,
-       node_has_attribute_dashmatch,
-       node_has_attribute_includes,
-       node_has_attribute_prefix,
-       node_has_attribute_suffix,
-       node_has_attribute_substring,
-       node_is_root,
-       node_count_siblings,
-       node_is_empty,
-       node_is_link,
-       node_is_visited,
-       node_is_hover,
-       node_is_active,
-       node_is_focus,
-       node_is_enabled,
-       node_is_disabled,
-       node_is_checked,
-       node_is_target,
-       node_is_lang,
-       node_presentational_hint,
-       ua_default_for_property,
+       return CSS_OK;
 
-       set_libcss_node_data,
-       get_libcss_node_data,
-};
+}
 
-static css_error resolve_url(void *pw,
-               const char *base, lwc_string *rel, lwc_string **abs)
+static css_error node_id(void *pw, void *n,
+               lwc_string **id)
+{
+       node *node = n;
+       uint32_t i;
+       line_ctx *lc = pw;
+
+       for (i = 0; i < node->n_attrs; i++) {
+               bool amatch = false;
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, lc->attr_id, &amatch) ==
+                               lwc_error_ok);
+               if (amatch == true)
+                       break;
+       }
+
+       if (i != node->n_attrs)
+               *id = lwc_string_ref(node->attrs[i].value);
+       else
+               *id = NULL;
+
+       return CSS_OK;
+}
+
+static css_error named_ancestor_node(void *pw, void *n,
+               const css_qname *qname,
+               void **ancestor)
 {
+       node *node = n;
        UNUSED(pw);
-       UNUSED(base);
 
-       /* About as useless as possible */
-       *abs = lwc_string_ref(rel);
+       for (node = node->parent; node != NULL; node = node->parent) {
+               bool match = false;
+               assert(lwc_string_caseless_isequal(
+                               qname->name, node->name,
+                               &match) == lwc_error_ok);
+               if (match == true)
+                       break;
+       }
+
+       *ancestor = (void *) node;
 
        return CSS_OK;
 }
 
-static bool fail_because_lwc_leaked = false;
+static css_error named_parent_node(void *pw, void *n,
+               const css_qname *qname,
+               void **parent)
+{
+       node *node = n;
+       UNUSED(pw);
 
-static void
-printing_lwc_iterator(lwc_string *str, void *pw)
+       *parent = NULL;
+       if (node->parent != NULL) {
+               bool match = false;
+               assert(lwc_string_caseless_isequal(
+                               qname->name, node->parent->name, &match) ==
+                               lwc_error_ok);
+               if (match == true)
+                       *parent = (void *) node->parent;
+       }
+
+       return CSS_OK;
+}
+
+static css_error named_sibling_node(void *pw, void *n,
+               const css_qname *qname,
+               void **sibling)
 {
+       node *node = n;
        UNUSED(pw);
 
-       printf(" DICT: %*s\n", (int)(lwc_string_length(str)), 
lwc_string_data(str));
-       fail_because_lwc_leaked = true;
+       *sibling = NULL;
+       if (node->prev != NULL) {
+               bool match = false;
+               assert(lwc_string_caseless_isequal(
+                               qname->name, node->prev->name, &match) ==
+                               lwc_error_ok);
+               if (match == true)
+                       *sibling = (void *) node->prev;
+       }
+
+       return CSS_OK;
 }
 
-int main(int argc, char **argv)
+static css_error named_generic_sibling_node(void *pw, void *n,
+               const css_qname *qname,
+               void **sibling)
 {
-       line_ctx ctx;
+       node *node = n;
+       UNUSED(pw);
 
-       if (argc != 2) {
-               printf("Usage: %s <filename>\n", argv[0]);
-               return 1;
+       for (node = node->prev; node != NULL; node = node->prev) {
+               bool match = false;
+               assert(lwc_string_caseless_isequal(
+                               qname->name, node->name,
+                               &match) == lwc_error_ok);
+               if (match == true)
+                       break;
        }
 
-       memset(&ctx, 0, sizeof(ctx));
+       *sibling = (void *) node;
 
+       return CSS_OK;
+}
 
-       lwc_intern_string("class", SLEN("class"), &ctx.attr_class);
-       lwc_intern_string("id", SLEN("id"), &ctx.attr_id);
+static css_error parent_node(void *pw, void *n, void **parent)
+{
+       node *node = n;
 
-       assert(css__parse_testfile(argv[1], handle_line, &ctx) == true);
+       UNUSED(pw);
 
-       /* and run final test */
-       if (ctx.tree != NULL)
-               run_test(&ctx, ctx.exp, ctx.expused);
+       *parent = (void *) node->parent;
 
-       free(ctx.exp);
+       return CSS_OK;
+}
 
-       lwc_string_unref(ctx.attr_class);
-       lwc_string_unref(ctx.attr_id);
+static css_error sibling_node(void *pw, void *n, void **sibling)
+{
+       node *node = n;
 
-       lwc_iterate_strings(printing_lwc_iterator, NULL);
+       UNUSED(pw);
 
-       assert(fail_because_lwc_leaked == false);
+       *sibling = (void *) node->prev;
 
-       printf("PASS\n");
-       return 0;
+       return CSS_OK;
 }
 
-bool handle_line(const char *data, size_t datalen, void *pw)
+static css_error node_has_name(void *pw, void *n,
+               const css_qname *qname,
+               bool *match)
 {
-       line_ctx *ctx = (line_ctx *) pw;
-       css_error error;
+       node *node = n;
+       UNUSED(pw);
 
-       if (data[0] == '#') {
-               if (ctx->intree) {
-                       if (strncasecmp(data+1, "errors", 6) == 0) {
-                               ctx->intree = false;
-                               ctx->insheet = false;
-                               ctx->inerrors = true;
-                               ctx->inexp = false;
-                       } else {
-                               /* Assume start of stylesheet */
-                               css__parse_sheet(ctx, data + 1, datalen - 1);
+       if (lwc_string_length(qname->name) == 1 &&
+                       lwc_string_data(qname->name)[0] == '*')
+               *match = true;
+       else
+               assert(lwc_string_caseless_isequal(node->name,
+                       qname->name, match) == lwc_error_ok);
 
-                               ctx->intree = false;
-                               ctx->insheet = true;
-                               ctx->inerrors = false;
-                               ctx->inexp = false;
-                       }
-               } else if (ctx->insheet) {
-                       if (strncasecmp(data+1, "errors", 6) == 0) {
-                               assert(css_stylesheet_data_done(
-                                       ctx->sheets[ctx->n_sheets - 1].sheet)
-                                       == CSS_OK);
+       return CSS_OK;
+}
 
-                               ctx->intree = false;
-                               ctx->insheet = false;
-                               ctx->inerrors = true;
-                               ctx->inexp = false;
-                       } else if (strncasecmp(data+1, "ua", 2) == 0 ||
-                                       strncasecmp(data+1, "user", 4) == 0 ||
-                                       strncasecmp(data+1, "author", 6) == 0) {
-                               assert(css_stylesheet_data_done(
-                                       ctx->sheets[ctx->n_sheets - 1].sheet)
-                                       == CSS_OK);
+static css_error node_has_class(void *pw, void *n,
+               lwc_string *name,
+               bool *match)
+{
+       node *node = n;
+       uint32_t i;
+       line_ctx *ctx = pw;
 
-                               css__parse_sheet(ctx, data + 1, datalen - 1);
-                       } else {
-                               error = css_stylesheet_append_data(
-                                       ctx->sheets[ctx->n_sheets - 1].sheet,
-                                       (const uint8_t *) data,
-                                       datalen);
-                               assert(error == CSS_OK ||
-                                               error == CSS_NEEDDATA);
-                       }
-               } else if (ctx->inerrors) {
-                       ctx->intree = false;
-                       ctx->insheet = false;
-                       ctx->inerrors = false;
-                       ctx->inexp = true;
-               } else if (ctx->inexp) {
-                       /* This marks end of testcase, so run it */
-                       run_test(ctx, ctx->exp, ctx->expused);
-
-                       ctx->expused = 0;
-
-                       ctx->intree = false;
-                       ctx->insheet = false;
-                       ctx->inerrors = false;
-                       ctx->inexp = false;
-               } else {
-                       /* Start state */
-                       if (strncasecmp(data+1, "tree", 4) == 0) {
-                               css__parse_tree(ctx, data + 5, datalen - 5);
-
-                               ctx->intree = true;
-                               ctx->insheet = false;
-                               ctx->inerrors = false;
-                               ctx->inexp = false;
-                       }
-               }
-       } else {
-               if (ctx->intree) {
-                       /* Not interested in the '|' */
-                       css__parse_tree_data(ctx, data + 1, datalen - 1);
-               } else if (ctx->insheet) {
-                       error = css_stylesheet_append_data(
-                                       ctx->sheets[ctx->n_sheets - 1].sheet,
-                                       (const uint8_t *) data, datalen);
-                       assert(error == CSS_OK || error == CSS_NEEDDATA);
-               } else if (ctx->inexp) {
-                       css__parse_expected(ctx, data, datalen);
-               }
+       for (i = 0; i < node->n_attrs; i++) {
+               bool amatch = false;
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, ctx->attr_class,
+                               &amatch) == lwc_error_ok);
+               if (amatch == true)
+                       break;
        }
 
-       return true;
+       /* Classes are case-sensitive in HTML */
+       if (i != node->n_attrs && name == node->attrs[i].value)
+               *match = true;
+       else
+               *match = false;
+
+       return CSS_OK;
 }
 
-void css__parse_tree(line_ctx *ctx, const char *data, size_t len)
+static css_error node_has_id(void *pw, void *n,
+               lwc_string *name,
+               bool *match)
 {
-       const char *p = data;
-       const char *end = data + len;
-       size_t left;
-
-       /* [ <media_list> <pseudo>? ] ? */
-
-       ctx->media.type = CSS_MEDIA_ALL;
-       ctx->pseudo_element = CSS_PSEUDO_ELEMENT_NONE;
-
-       /* Consume any leading whitespace */
-       while (p < end && isspace(*p))
-               p++;
-
-       if (p < end) {
-               left = end - p;
-
-               css__parse_media_list(&p, &left, &ctx->media);
+       node *node = n;
+       uint32_t i;
+       line_ctx *ctx = pw;
 
-               end = p + left;
+       for (i = 0; i < node->n_attrs; i++) {
+               bool amatch = false;
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, ctx->attr_id, &amatch) ==
+                               lwc_error_ok);
+               if (amatch == true)
+                       break;
        }
 
-       if (p < end) {
-               left = end - p;
+       /* IDs are case-sensitive in HTML */
+       if (i != node->n_attrs && name == node->attrs[i].value)
+               *match = true;
+       else
+               *match = false;
 
-               css__parse_pseudo_list(&p, &left, &ctx->pseudo_element);
-       }
+       return CSS_OK;
 }
 
-void css__parse_tree_data(line_ctx *ctx, const char *data, size_t len)
+static css_error node_has_attribute(void *pw, void *n,
+               const css_qname *qname,
+               bool *match)
 {
-       const char *p = data;
-       const char *end = data + len;
-       const char *name = NULL;
-       const char *value = NULL;
-       size_t namelen = 0;
-       size_t valuelen = 0;
-       uint32_t depth = 0;
-       bool target = false;
-
-       /* ' '{depth+1} [ <element> '*'? | <attr> ]
-        *
-        * <element> ::= [^=*[:space:]]+
-        * <attr>    ::= [^=*[:space:]]+ '=' [^[:space:]]*
-        */
-
-       while (p < end && isspace(*p)) {
-               depth++;
-               p++;
-       }
-       depth--;
-
-       /* Get element/attribute name */
-       name = p;
-       while (p < end && *p != '=' && *p != '*' && isspace(*p) == false) {
-               namelen++;
-               p++;
-       }
-
-       /* Skip whitespace */
-       while (p < end && isspace(*p))
-               p++;
-
-       if (p < end && *p == '=') {
-               /* Attribute value */
-               p++;
-
-               value = p;
-
-               while (p < end && isspace(*p) == false) {
-                       valuelen++;
-                       p++;
-               }
-       } else if (p < end && *p == '*') {
-               /* Element is target node */
-               target = true;
-       }
-
-       if (value == NULL) {
-               /* We have an element, so create it */
-               node *n = malloc(sizeof(node));
-               assert(n != NULL);
-
-               memset(n, 0, sizeof(node));
-
-               lwc_intern_string(name, namelen, &n->name);
-
-               /* Insert it into tree */
-               if (ctx->tree == NULL) {
-                       ctx->tree = n;
-               } else {
-                       assert(depth > 0);
-                       assert(depth <= ctx->depth + 1);
-
-                       /* Find node to insert into */
-                       while (depth <= ctx->depth) {
-                               ctx->depth--;
-                               ctx->current = ctx->current->parent;
-                       }
-
-                       /* Insert into current node */
-                       if (ctx->current->children == NULL) {
-                               ctx->current->children = n;
-                               ctx->current->last_child = n;
-                       } else {
-                               ctx->current->last_child->next = n;
-                               n->prev = ctx->current->last_child;
-
-                               ctx->current->last_child = n;
-                       }
-                       n->parent = ctx->current;
-               }
-
-               ctx->current = n;
-               ctx->depth = depth;
-
-               /* Mark the target, if it's us */
-               if (target)
-                       ctx->target = n;
-       } else {
-               /* New attribute */
-               bool amatch = false;
-               attribute *attr;
-               node *n = ctx->current;
-
-               attribute *temp = realloc(n->attrs,
-                               (n->n_attrs + 1) * sizeof(attribute));
-               assert(temp != NULL);
-
-               n->attrs = temp;
-
-               attr = &n->attrs[n->n_attrs];
-
-               lwc_intern_string(name, namelen, &attr->name);
-               lwc_intern_string(value, valuelen, &attr->value);
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
 
+       *match = false;
+       for (i = 0; i < node->n_attrs; i++) {
                assert(lwc_string_caseless_isequal(
-                               n->attrs[n->n_attrs].name,
-                               ctx->attr_class, &amatch) == lwc_error_ok);
-               if (amatch == true) {
-                       n->classes = realloc(NULL, sizeof(lwc_string **));
-                       assert(n->classes != NULL);
-
-                       n->classes[0] = lwc_string_ref(
-                                       n->attrs[n->n_attrs].
-                                       value);
-                       n->n_classes = 1;
-               }
-
-               n->n_attrs++;
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
        }
+
+       return CSS_OK;
 }
 
-static css_error css_font_resolution_func(void *pw, lwc_string *name,
-               css_system_font *system_font)
+static css_error node_has_attribute_equal(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
 {
-       lwc_error err;
-
-       if (system_font == NULL) {
-               return CSS_BADPARM;
-       }
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
 
-       (void)(pw);
+       *match = false;
 
-       if (strncmp(lwc_string_data(name), "special-system-font",
-                       lwc_string_length(name)) != 0) {
-               return CSS_INVALID;
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
        }
 
-       system_font->style = CSS_FONT_STYLE_NORMAL;
-       system_font->variant = CSS_FONT_VARIANT_NORMAL;
-       system_font->weight = CSS_FONT_WEIGHT_NORMAL;
-       system_font->size.size = INTTOFIX(22);
-       system_font->size.unit = CSS_UNIT_PT;
-       system_font->line_height.size = INTTOFIX(33);
-       system_font->line_height.unit = CSS_UNIT_EM;
-       err = lwc_intern_string("special-system-font",
-                       strlen("special-system-font"),
-                       &system_font->family);
-       if (err != lwc_error_ok) {
-               return CSS_NOMEM;
+       if (*match == true) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, value, match) ==
+                               lwc_error_ok);
        }
 
        return CSS_OK;
 }
 
-void css__parse_sheet(line_ctx *ctx, const char *data, size_t len)
+static css_error node_has_attribute_includes(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
 {
-       css_stylesheet_params params;
-       const char *p;
-       const char *end = data + len;
-       css_origin origin = CSS_ORIGIN_AUTHOR;
-       css_stylesheet *sheet;
-       sheet_ctx *temp;
-       char *media = NULL;
+       node *node = n;
+       uint32_t i;
+       size_t vlen = lwc_string_length(value);
+       UNUSED(pw);
 
-       /* <origin> <media_list>? */
+       *match = false;
 
-       /* Find end of origin */
-       for (p = data; p < end; p++) {
-               if (isspace(*p))
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
                        break;
        }
 
-       if (p - data == 6 && strncasecmp(data, "author", 6) == 0)
-               origin = CSS_ORIGIN_AUTHOR;
-       else if (p - data == 4 && strncasecmp(data, "user", 4) == 0)
-               origin = CSS_ORIGIN_USER;
-       else if (p - data == 2 && strncasecmp(data, "ua", 2) == 0)
-               origin = CSS_ORIGIN_UA;
-       else
-               assert(0 && "Unknown stylesheet origin");
-
-       /* Skip any whitespace */
-       while (p < end && isspace(*p))
-               p++;
-
-       assert(end >= p);
-       media = malloc(end - p + 1);
-       assert(media != NULL);
-       memcpy(media, p, end - p);
-       media[end - p] = '\0';
-
-       params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
-       params.level = CSS_LEVEL_21;
-       params.charset = "UTF-8";
-       params.url = "foo";
-       params.title = "foo";
-       params.allow_quirks = false;
-       params.inline_style = false;
-       params.resolve = resolve_url;
-       params.resolve_pw = NULL;
-       params.import = NULL;
-       params.import_pw = NULL;
-       params.color = NULL;
-       params.color_pw = NULL;
-       params.font = css_font_resolution_func;
-       params.font_pw = NULL;
-
-       /** \todo How are we going to handle @import? */
-       assert(css_stylesheet_create(&params, &sheet) == CSS_OK);
-
-       /* Extend array of sheets and append new sheet to it */
-       temp = realloc(ctx->sheets,
-                       (ctx->n_sheets + 1) * sizeof(sheet_ctx));
-       assert(temp != NULL);
-
-       ctx->sheets = temp;
-
-       ctx->sheets[ctx->n_sheets].sheet = sheet;
-       ctx->sheets[ctx->n_sheets].origin = origin;
-       ctx->sheets[ctx->n_sheets].media = media;
-
-       ctx->n_sheets++;
-}
-
-void css__parse_media_list(const char **data, size_t *len, css_media *media)
-{
-       const char *p = *data;
-       const char *end = p + *len;
-       uint64_t result = 0;
-
-       /* <medium> [ ',' <medium> ]* */
-
-       while (p < end) {
-               const char *start = p;
+       if (*match == true) {
+               const char *p;
+               const char *start = lwc_string_data(node->attrs[i].value);
+               const char *end = start +
+                               lwc_string_length(node->attrs[i].value);
 
-               /* consume a medium */
-               while (isspace(*p) == false && *p != ',')
-                       p++;
+               *match = false;
 
-               if (p - start == 10 &&
-                               strncasecmp(start, "projection", 10) == 0)
-                       result |= CSS_MEDIA_PROJECTION;
-               else if (p - start == 8 &&
-                               strncasecmp(start, "handheld", 8) == 0)
-                       result |= CSS_MEDIA_HANDHELD;
-               else if (p - start == 8 &&
-                               strncasecmp(start, "embossed", 8) == 0)
-                       result |= CSS_MEDIA_EMBOSSED;
-               else if (p - start == 7 &&
-                               strncasecmp(start, "braille", 7) == 0)
-                       result |= CSS_MEDIA_BRAILLE;
-               else if (p - start == 6 &&
-                               strncasecmp(start, "speech", 6) == 0)
-                       result |= CSS_MEDIA_SPEECH;
-               else if (p - start == 6 &&
-                               strncasecmp(start, "screen", 6) == 0)
-                       result |= CSS_MEDIA_SCREEN;
-               else if (p - start == 5 &&
-                               strncasecmp(start, "print", 5) == 0)
-                       result |= CSS_MEDIA_PRINT;
-               else if (p - start == 5 &&
-                               strncasecmp(start, "aural", 5) == 0)
-                       result |= CSS_MEDIA_AURAL;
-               else if (p - start == 3 &&
-                               strncasecmp(start, "tty", 3) == 0)
-                       result |= CSS_MEDIA_TTY;
-               else if (p - start == 3 &&
-                               strncasecmp(start, "all", 3) == 0)
-                       result |= CSS_MEDIA_ALL;
-               else if (p - start == 2 &&
-                               strncasecmp(start, "tv", 2) == 0)
-                       result |= CSS_MEDIA_TV;
-               else
-                       assert(0 && "Unknown media type");
+               for (p = start; p < end; p++) {
+                       if (*p == ' ') {
+                               if ((size_t) (p - start) == vlen &&
+                                               strncasecmp(start,
+                                                       lwc_string_data(value),
+                                                       vlen) == 0) {
+                                       *match = true;
+                                       break;
+                               }
 
-               /* Consume whitespace */
-               while (p < end && isspace(*p))
-                       p++;
+                               start = p + 1;
+                       }
+               }
+       }
 
-               /* Stop if we've reached the end */
-               if (p == end || *p != ',')
-                       break;
+       return CSS_OK;
+}
 
-               /* Consume comma */
-               p++;
+static css_error node_has_attribute_dashmatch(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
+{
+       node *node = n;
+       uint32_t i;
+       size_t vlen = lwc_string_length(value);
+       UNUSED(pw);
 
-               /* Consume whitespace */
-               while (p < end && isspace(*p))
-                       p++;
+       *match = false;
+
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
        }
 
-       media->type = result;
+       if (*match == true) {
+               const char *p;
+               const char *start = lwc_string_data(node->attrs[i].value);
+               const char *end = start +
+                               lwc_string_length(node->attrs[i].value);
 
-       *data = p;
-       *len = end - p;
+               *match = false;
+
+               for (p = start; p < end; p++) {
+                       if (*p == '-') {
+                               if ((size_t) (p - start) == vlen &&
+                                               strncasecmp(start,
+                                                       lwc_string_data(value),
+                                                       vlen) == 0) {
+                                       *match = true;
+                                       break;
+                               }
+
+                               start = p + 1;
+                       }
+               }
+       }
+
+       return CSS_OK;
 }
 
-void css__parse_pseudo_list(const char **data, size_t *len, uint32_t *element)
+static css_error node_has_attribute_prefix(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
 {
-       const char *p = *data;
-       const char *end = p + *len;
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
 
-       /* <pseudo> [ ',' <pseudo> ]* */
+       *match = false;
 
-       *element = CSS_PSEUDO_ELEMENT_NONE;
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
+       }
 
-       while (p < end) {
-               const char *start = p;
+       if (*match == true) {
+               size_t len = lwc_string_length(node->attrs[i].value);
+               const char *data = lwc_string_data(node->attrs[i].value);
 
-               /* consume a pseudo */
-               while (isspace(*p) == false && *p != ',')
-                       p++;
+               size_t vlen = lwc_string_length(value);
+               const char *vdata = lwc_string_data(value);
 
-               /* Pseudo elements */
-               if (p - start == 12 &&
-                               strncasecmp(start, "first-letter", 12) == 0)
-                       *element = CSS_PSEUDO_ELEMENT_FIRST_LETTER;
-               else if (p - start == 10 &&
-                               strncasecmp(start, "first-line", 10) == 0)
-                       *element = CSS_PSEUDO_ELEMENT_FIRST_LINE;
-               else if (p - start == 6 &&
-                               strncasecmp(start, "before", 6) == 0)
-                       *element = CSS_PSEUDO_ELEMENT_BEFORE;
-               else if (p - start == 5 &&
-                               strncasecmp(start, "after", 5) == 0)
-                       *element = CSS_PSEUDO_ELEMENT_AFTER;
+               if (len < vlen)
+                       *match = false;
                else
-                       assert(0 && "Unknown pseudo");
+                       *match = (strncasecmp(data, vdata, vlen) == 0);
+       }
 
-               /* Consume whitespace */
-               while (p < end && isspace(*p))
-                       p++;
+       return CSS_OK;
+}
 
-               /* Stop if we've reached the end */
-               if (p == end || *p != ',')
+static css_error node_has_attribute_suffix(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
+{
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
+
+       *match = false;
+
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
                        break;
+       }
 
-               /* Consume comma */
-               p++;
+       if (*match == true) {
+               size_t len = lwc_string_length(node->attrs[i].value);
+               const char *data = lwc_string_data(node->attrs[i].value);
 
-               /* Consume whitespace */
-               while (p < end && isspace(*p))
-                       p++;
+               size_t vlen = lwc_string_length(value);
+               const char *vdata = lwc_string_data(value);
+
+               size_t suffix_start = len - vlen;
+
+               if (len < vlen)
+                       *match = false;
+               else {
+                       *match = (strncasecmp(data + suffix_start,
+                                       vdata, vlen) == 0);
+               }
        }
 
-       *data = p;
-       *len = end - p;
+       return CSS_OK;
 }
 
-void css__parse_expected(line_ctx *ctx, const char *data, size_t len)
+static css_error node_has_attribute_substring(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
 {
-       while (ctx->expused + len >= ctx->explen) {
-               size_t required = ctx->explen == 0 ? 64 : ctx->explen * 2;
-               char *temp = realloc(ctx->exp, required);
-               if (temp == NULL) {
-                       assert(0 && "No memory for expected output");
-               }
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
 
-               ctx->exp = temp;
-               ctx->explen = required;
+       *match = false;
+
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
        }
 
-       memcpy(ctx->exp + ctx->expused, data, len);
+       if (*match == true) {
+               size_t len = lwc_string_length(node->attrs[i].value);
+               const char *data = lwc_string_data(node->attrs[i].value);
 
-       ctx->expused += len;
-}
+               size_t vlen = lwc_string_length(value);
+               const char *vdata = lwc_string_data(value);
 
-static void show_differences(size_t len, const char *exp, const char *res)
-{
-       const char *pos_exp, *opos_exp;
-       const char *pos_res, *opos_res;
+               const char *last_start = data + len - vlen;
 
-       opos_exp = pos_exp = exp;
-       opos_res = pos_res = res;
+               if (len < vlen)
+                       *match = false;
+               else {
+                       while (data <= last_start) {
+                               if (strncasecmp(data, vdata, vlen) == 0) {
+                                       *match = true;
+                                       break;
+                               }
 
-       printf("Line differences:\n");
-       while (pos_exp < exp + len && pos_res < res + len) {
-               if (*pos_exp == '\n' && *pos_res == '\n') {
-                       if (pos_exp - opos_exp != pos_res - opos_res ||
-                                       memcmp(opos_exp, opos_res,
-                                       pos_exp - opos_exp) != 0) {
-                               printf("Expected:\t%.*s\n",
-                                               (int)(pos_exp - opos_exp),
-                                               opos_exp);
-                               printf("  Result:\t%.*s\n",
-                                               (int)(pos_res - opos_res),
-                                               opos_res);
-                               printf("\n");
+                               data++;
                        }
-                       opos_exp = ++pos_exp;
-                       opos_res = ++pos_res;
-               } else if (*pos_exp == '\n') {
-                       pos_res++;
-               } else if (*pos_res == '\n') {
-                       pos_exp++;
-               } else {
-                       pos_exp++;
-                       pos_res++;
+
+                       if (data > last_start)
+                               *match = false;
                }
        }
+
+       return CSS_OK;
 }
 
+static css_error node_is_root(void *pw, void *n, bool *match)
+{
+       node *node = n;
+       UNUSED(pw);
 
-static void run_test_select_tree(css_select_ctx *select,
-               node *node, line_ctx *ctx,
-               char *buf, size_t *buflen)
+       *match = (node->parent == NULL);
+
+       return CSS_OK;
+}
+
+static css_error node_count_siblings(void *pw, void *n,
+               bool same_name, bool after, int32_t *count)
 {
-       css_select_results *sr;
-       struct node *n = NULL;
+       int32_t cnt = 0;
+       bool match = false;
+       node *node = n;
+       lwc_string *name = node->name;
+       UNUSED(pw);
 
-       if (node->parent == NULL) {
-               unit_ctx.root_style = NULL;
-       }
+       if (after) {
+               while (node->next != NULL) {
+                       if (same_name) {
+                               assert(lwc_string_caseless_isequal(
+                                       name, node->next->name, &match) ==
+                                       lwc_error_ok);
+
+                               if (match)
+                                       cnt++;
+                       } else {
+                               cnt++;
+                       }
 
+                       node = node->next;
+               }
+       } else {
+               while (node->prev != NULL) {
+                       if (same_name) {
+                               assert(lwc_string_caseless_isequal(
+                                       name, node->prev->name, &match) ==
+                                       lwc_error_ok);
 
-       assert(css_select_style(select, node, &unit_ctx, &ctx->media, NULL,
-                       &select_handler, ctx, &sr) == CSS_OK);
+                               if (match)
+                                       cnt++;
+                       } else {
+                               cnt++;
+                       }
 
-       if (node->parent != NULL) {
-               css_computed_style *composed;
-               assert(css_computed_style_compose(
-                               node->parent->sr->styles[ctx->pseudo_element],
-                               sr->styles[ctx->pseudo_element],
-                               &unit_ctx,
-                               &composed) == CSS_OK);
-               css_computed_style_destroy(sr->styles[ctx->pseudo_element]);
-               sr->styles[ctx->pseudo_element] = composed;
+                       node = node->prev;
+               }
        }
 
-       node->sr = sr;
+       *count = cnt;
 
-       if (node == ctx->target) {
-               dump_computed_style(sr->styles[ctx->pseudo_element],
-                               buf, buflen);
-       }
+       return CSS_OK;
+}
 
-       if (node->parent == NULL) {
-               unit_ctx.root_style = node->sr->styles[ctx->pseudo_element];
-       }
+static css_error node_is_empty(void *pw, void *n, bool *match)
+{
+       node *node = n;
+       UNUSED(pw);
 
-       for (n = node->children; n != NULL; n = n->next) {
-               run_test_select_tree(select, n, ctx, buf, buflen);
-       }
-}
+       *match = (node->children == NULL);
 
+       return CSS_OK;
+}
 
-void run_test(line_ctx *ctx, const char *exp, size_t explen)
+static css_error node_is_link(void *pw, void *n, bool *match)
 {
-       css_select_ctx *select;
-       css_select_results *results;
-       uint32_t i;
-       char *buf;
-       size_t buflen;
-       static int testnum;
+       node *node = n;
 
-       UNUSED(exp);
+       UNUSED(pw);
+       UNUSED(node);
 
-       buf = malloc(8192);
-       if (buf == NULL) {
-               assert(0 && "No memory for result data");
-       }
-       buflen = 8192;
+       *match = false;
 
-       assert(css_select_ctx_create(&select) == CSS_OK);
+       return CSS_OK;
+}
 
-       for (i = 0; i < ctx->n_sheets; i++) {
-               assert(css_select_ctx_append_sheet(select,
-                               ctx->sheets[i].sheet, ctx->sheets[i].origin,
-                               ctx->sheets[i].media) == CSS_OK);
-       }
+static css_error node_is_visited(void *pw, void *n, bool *match)
+{
+       node *node = n;
 
-       testnum++;
+       UNUSED(pw);
+       UNUSED(node);
 
-       run_test_select_tree(select, ctx->tree, ctx, buf, &buflen);
+       *match = false;
 
-       results = ctx->target->sr;
-       assert(results->styles[ctx->pseudo_element] != NULL);
+       return CSS_OK;
+}
 
-       if (8192 - buflen != explen || memcmp(buf, exp, explen) != 0) {
-               size_t len = 8192 - buflen < explen ? 8192 - buflen : explen;
-               printf("Expected (%u):\n%.*s\n",
-                               (int) explen, (int) explen, exp);
-               printf("Result (%u):\n%.*s\n", (int) (8192 - buflen),
-                       (int) (8192 - buflen), buf);
+static css_error node_is_hover(void *pw, void *n, bool *match)
+{
+       node *node = n;
 
-               show_differences(len, exp, buf);
-               assert(0 && "Result doesn't match expected");
-       }
+       UNUSED(pw);
+       UNUSED(node);
 
-       /* Clean up */
-       css_select_ctx_destroy(select);
-       destroy_tree(ctx->tree);
+       *match = false;
 
-       for (i = 0; i < ctx->n_sheets; i++) {
-               css_stylesheet_destroy(ctx->sheets[i].sheet);
-               free(ctx->sheets[i].media);
-       }
+       return CSS_OK;
+}
 
-       ctx->tree = NULL;
-       ctx->current = NULL;
-       ctx->depth = 0;
-       ctx->n_sheets = 0;
-       free(ctx->sheets);
-       ctx->sheets = NULL;
-       ctx->target = NULL;
+static css_error node_is_active(void *pw, void *n, bool *match)
+{
+       node *node = n;
 
-       free(buf);
+       UNUSED(pw);
+       UNUSED(node);
 
-       printf("Test %d: PASS\n", testnum);
+       *match = false;
+
+       return CSS_OK;
 }
 
-void destroy_tree(node *root)
+static css_error node_is_focus(void *pw, void *n, bool *match)
 {
-       node *n, *p;
-       uint32_t i;
+       node *node = n;
 
-       for (n = root->children; n != NULL; n = p) {
-               p = n->next;
+       UNUSED(pw);
+       UNUSED(node);
 
-               destroy_tree(n);
-       }
+       *match = false;
 
-       css_select_results_destroy(root->sr);
+       return CSS_OK;
+}
 
-       for (i = 0; i < root->n_attrs; ++i) {
-               lwc_string_unref(root->attrs[i].name);
-               lwc_string_unref(root->attrs[i].value);
-       }
-       free(root->attrs);
+static css_error node_is_enabled(void *pw, void *n, bool *match)
+{
+       node *node = n;
 
-       if (root->classes != NULL) {
-               for (i = 0; i < root->n_classes; ++i) {
-                       lwc_string_unref(root->classes[i]);
-               }
-               free(root->classes);
-       }
+       UNUSED(pw);
+       UNUSED(node);
 
-       if (root->libcss_node_data != NULL) {
-               css_libcss_node_data_handler(&select_handler, CSS_NODE_DELETED,
-                               NULL, root, NULL, root->libcss_node_data);
-       }
+       *match = false;
 
-       lwc_string_unref(root->name);
-       free(root);
+       return CSS_OK;
 }
 
-
-css_error node_name(void *pw, void *n, css_qname *qname)
+static css_error node_is_disabled(void *pw, void *n, bool *match)
 {
        node *node = n;
 
        UNUSED(pw);
+       UNUSED(node);
 
-       qname->name = lwc_string_ref(node->name);
+       *match = false;
 
        return CSS_OK;
 }
 
-static css_error node_classes(void *pw, void *n,
-               lwc_string ***classes, uint32_t *n_classes)
+static css_error node_is_checked(void *pw, void *n, bool *match)
 {
-       unsigned int i;
        node *node = n;
-       UNUSED(pw);
 
-       *classes = node->classes;
-       *n_classes = node->n_classes;
+       UNUSED(pw);
+       UNUSED(node);
 
-       for (i = 0; i < *n_classes; i++)
-               (*classes)[i] = lwc_string_ref(node->classes[i]);
+       *match = false;
 
        return CSS_OK;
-
 }
 
-css_error node_id(void *pw, void *n,
-               lwc_string **id)
+static css_error node_is_target(void *pw, void *n, bool *match)
 {
        node *node = n;
-       uint32_t i;
-       line_ctx *lc = pw;
 
-       for (i = 0; i < node->n_attrs; i++) {
-               bool amatch = false;
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, lc->attr_id, &amatch) ==
-                               lwc_error_ok);
-               if (amatch == true)
-                       break;
-       }
+       UNUSED(pw);
+       UNUSED(node);
 
-       if (i != node->n_attrs)
-               *id = lwc_string_ref(node->attrs[i].value);
-       else
-               *id = NULL;
+       *match = false;
 
        return CSS_OK;
 }
 
-css_error named_ancestor_node(void *pw, void *n,
-               const css_qname *qname,
-               void **ancestor)
+static css_error node_is_lang(void *pw, void *n,
+               lwc_string *lang,
+               bool *match)
 {
        node *node = n;
-       UNUSED(pw);
 
-       for (node = node->parent; node != NULL; node = node->parent) {
-               bool match = false;
-               assert(lwc_string_caseless_isequal(
-                               qname->name, node->name,
-                               &match) == lwc_error_ok);
-               if (match == true)
-                       break;
-       }
+       UNUSED(pw);
+       UNUSED(node);
+       UNUSED(lang);
 
-       *ancestor = (void *) node;
+       *match = false;
 
        return CSS_OK;
 }
 
-css_error named_parent_node(void *pw, void *n,
-               const css_qname *qname,
-               void **parent)
+static css_error node_presentational_hint(void *pw, void *node,
+               uint32_t *nhints, css_hint **hints)
 {
-       node *node = n;
        UNUSED(pw);
+       UNUSED(node);
 
-       *parent = NULL;
-       if (node->parent != NULL) {
-               bool match = false;
-               assert(lwc_string_caseless_isequal(
-                               qname->name, node->parent->name, &match) ==
-                               lwc_error_ok);
-               if (match == true)
-                       *parent = (void *) node->parent;
-       }
+       *nhints = 0;
+       *hints = NULL;
 
        return CSS_OK;
 }
 
-css_error named_sibling_node(void *pw, void *n,
-               const css_qname *qname,
-               void **sibling)
+static css_error ua_default_for_property(void *pw, uint32_t property, css_hint 
*hint)
 {
-       node *node = n;
        UNUSED(pw);
 
-       *sibling = NULL;
-       if (node->prev != NULL) {
-               bool match = false;
-               assert(lwc_string_caseless_isequal(
-                               qname->name, node->prev->name, &match) ==
-                               lwc_error_ok);
-               if (match == true)
-                       *sibling = (void *) node->prev;
+       if (property == CSS_PROP_COLOR) {
+               hint->data.color = 0xff000000;
+               hint->status = CSS_COLOR_COLOR;
+       } else if (property == CSS_PROP_FONT_FAMILY) {
+               hint->data.strings = NULL;
+               hint->status = CSS_FONT_FAMILY_SANS_SERIF;
+       } else if (property == CSS_PROP_QUOTES) {
+               /* Not exactly useful :) */
+               hint->data.strings = NULL;
+               hint->status = CSS_QUOTES_NONE;
+       } else if (property == CSS_PROP_VOICE_FAMILY) {
+               /** \todo Fix this when we have voice-family done */
+               hint->data.strings = NULL;
+               hint->status = 0;
+       } else {
+               return CSS_INVALID;
        }
 
        return CSS_OK;
 }
 
-css_error named_generic_sibling_node(void *pw, void *n,
-               const css_qname *qname,
-               void **sibling)
+static css_error set_libcss_node_data(void *pw, void *n,
+               void *libcss_node_data)
 {
        node *node = n;
        UNUSED(pw);
 
-       for (node = node->prev; node != NULL; node = node->prev) {
-               bool match = false;
-               assert(lwc_string_caseless_isequal(
-                               qname->name, node->name,
-                               &match) == lwc_error_ok);
-               if (match == true)
-                       break;
-       }
-
-       *sibling = (void *) node;
+       node->libcss_node_data = libcss_node_data;
 
        return CSS_OK;
 }
 
-css_error parent_node(void *pw, void *n, void **parent)
+static css_error get_libcss_node_data(void *pw, void *n,
+               void **libcss_node_data)
 {
        node *node = n;
-
        UNUSED(pw);
 
-       *parent = (void *) node->parent;
+       /* Pass any node data back to libcss */
+       *libcss_node_data = node->libcss_node_data;
 
        return CSS_OK;
 }
 
-css_error sibling_node(void *pw, void *n, void **sibling)
-{
-       node *node = n;
+static css_unit_ctx unit_ctx = {
+       .font_size_default = 16 * (1 << CSS_RADIX_POINT),
+};
+
+static css_select_handler select_handler = {
+       CSS_SELECT_HANDLER_VERSION_1,
+
+       node_name,
+       node_classes,
+       node_id,
+       named_ancestor_node,
+       named_parent_node,
+       named_sibling_node,
+       named_generic_sibling_node,
+       parent_node,
+       sibling_node,
+       node_has_name,
+       node_has_class,
+       node_has_id,
+       node_has_attribute,
+       node_has_attribute_equal,
+       node_has_attribute_dashmatch,
+       node_has_attribute_includes,
+       node_has_attribute_prefix,
+       node_has_attribute_suffix,
+       node_has_attribute_substring,
+       node_is_root,
+       node_count_siblings,
+       node_is_empty,
+       node_is_link,
+       node_is_visited,
+       node_is_hover,
+       node_is_active,
+       node_is_focus,
+       node_is_enabled,
+       node_is_disabled,
+       node_is_checked,
+       node_is_target,
+       node_is_lang,
+       node_presentational_hint,
+       ua_default_for_property,
 
+       set_libcss_node_data,
+       get_libcss_node_data,
+};
+
+static css_error resolve_url(void *pw,
+               const char *base, lwc_string *rel, lwc_string **abs)
+{
        UNUSED(pw);
+       UNUSED(base);
 
-       *sibling = (void *) node->prev;
+       /* About as useless as possible */
+       *abs = lwc_string_ref(rel);
 
        return CSS_OK;
 }
 
-css_error node_has_name(void *pw, void *n,
-               const css_qname *qname,
-               bool *match)
+static bool fail_because_lwc_leaked = false;
+
+static void
+printing_lwc_iterator(lwc_string *str, void *pw)
 {
-       node *node = n;
        UNUSED(pw);
 
-       if (lwc_string_length(qname->name) == 1 &&
-                       lwc_string_data(qname->name)[0] == '*')
-               *match = true;
-       else
-               assert(lwc_string_caseless_isequal(node->name,
-                       qname->name, match) == lwc_error_ok);
+       printf(" DICT: %*s\n", (int)(lwc_string_length(str)), 
lwc_string_data(str));
+       fail_because_lwc_leaked = true;
+}
+
+static css_error css_font_resolution_func(void *pw, lwc_string *name,
+               css_system_font *system_font)
+{
+       lwc_error err;
+
+       if (system_font == NULL) {
+               return CSS_BADPARM;
+       }
+
+       (void)(pw);
+
+       if (strncmp(lwc_string_data(name), "special-system-font",
+                       lwc_string_length(name)) != 0) {
+               return CSS_INVALID;
+       }
+
+       system_font->style = CSS_FONT_STYLE_NORMAL;
+       system_font->variant = CSS_FONT_VARIANT_NORMAL;
+       system_font->weight = CSS_FONT_WEIGHT_NORMAL;
+       system_font->size.size = INTTOFIX(22);
+       system_font->size.unit = CSS_UNIT_PT;
+       system_font->line_height.size = INTTOFIX(33);
+       system_font->line_height.unit = CSS_UNIT_EM;
+       err = lwc_intern_string("special-system-font",
+                       strlen("special-system-font"),
+                       &system_font->family);
+       if (err != lwc_error_ok) {
+               return CSS_NOMEM;
+       }
 
        return CSS_OK;
 }
 
-css_error node_has_class(void *pw, void *n,
-               lwc_string *name,
-               bool *match)
+static void css__parse_sheet(line_ctx *ctx, const char *data, size_t len)
 {
-       node *node = n;
-       uint32_t i;
-       line_ctx *ctx = pw;
+       css_stylesheet_params params;
+       const char *p;
+       const char *end = data + len;
+       css_origin origin = CSS_ORIGIN_AUTHOR;
+       css_stylesheet *sheet;
+       sheet_ctx *temp;
+       char *media = NULL;
 
-       for (i = 0; i < node->n_attrs; i++) {
-               bool amatch = false;
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, ctx->attr_class,
-                               &amatch) == lwc_error_ok);
-               if (amatch == true)
+       /* <origin> <media_list>? */
+
+       /* Find end of origin */
+       for (p = data; p < end; p++) {
+               if (isspace(*p))
                        break;
        }
 
-       /* Classes are case-sensitive in HTML */
-       if (i != node->n_attrs && name == node->attrs[i].value)
-               *match = true;
+       if (p - data == 6 && strncasecmp(data, "author", 6) == 0)
+               origin = CSS_ORIGIN_AUTHOR;
+       else if (p - data == 4 && strncasecmp(data, "user", 4) == 0)
+               origin = CSS_ORIGIN_USER;
+       else if (p - data == 2 && strncasecmp(data, "ua", 2) == 0)
+               origin = CSS_ORIGIN_UA;
        else
-               *match = false;
+               assert(0 && "Unknown stylesheet origin");
 
-       return CSS_OK;
+       /* Skip any whitespace */
+       while (p < end && isspace(*p))
+               p++;
+
+       assert(end >= p);
+       media = malloc(end - p + 1);
+       assert(media != NULL);
+       memcpy(media, p, end - p);
+       media[end - p] = '\0';
+
+       params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
+       params.level = CSS_LEVEL_21;
+       params.charset = "UTF-8";
+       params.url = "foo";
+       params.title = "foo";
+       params.allow_quirks = false;
+       params.inline_style = false;
+       params.resolve = resolve_url;
+       params.resolve_pw = NULL;
+       params.import = NULL;
+       params.import_pw = NULL;
+       params.color = NULL;
+       params.color_pw = NULL;
+       params.font = css_font_resolution_func;
+       params.font_pw = NULL;
+
+       /** \todo How are we going to handle @import? */
+       assert(css_stylesheet_create(&params, &sheet) == CSS_OK);
+
+       /* Extend array of sheets and append new sheet to it */
+       temp = realloc(ctx->sheets,
+                       (ctx->n_sheets + 1) * sizeof(sheet_ctx));
+       assert(temp != NULL);
+
+       ctx->sheets = temp;
+
+       ctx->sheets[ctx->n_sheets].sheet = sheet;
+       ctx->sheets[ctx->n_sheets].origin = origin;
+       ctx->sheets[ctx->n_sheets].media = media;
+
+       ctx->n_sheets++;
 }
 
-css_error node_has_id(void *pw, void *n,
-               lwc_string *name,
-               bool *match)
+static void css__parse_media_list(const char **data, size_t *len, css_media 
*media)
 {
-       node *node = n;
-       uint32_t i;
-       line_ctx *ctx = pw;
+       const char *p = *data;
+       const char *end = p + *len;
+       uint64_t result = 0;
 
-       for (i = 0; i < node->n_attrs; i++) {
-               bool amatch = false;
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, ctx->attr_id, &amatch) ==
-                               lwc_error_ok);
-               if (amatch == true)
-                       break;
-       }
+       /* <medium> [ ',' <medium> ]* */
+
+       while (p < end) {
+               const char *start = p;
+
+               /* consume a medium */
+               while (isspace(*p) == false && *p != ',')
+                       p++;
+
+               if (p - start == 10 &&
+                               strncasecmp(start, "projection", 10) == 0)
+                       result |= CSS_MEDIA_PROJECTION;
+               else if (p - start == 8 &&
+                               strncasecmp(start, "handheld", 8) == 0)
+                       result |= CSS_MEDIA_HANDHELD;
+               else if (p - start == 8 &&
+                               strncasecmp(start, "embossed", 8) == 0)
+                       result |= CSS_MEDIA_EMBOSSED;
+               else if (p - start == 7 &&
+                               strncasecmp(start, "braille", 7) == 0)
+                       result |= CSS_MEDIA_BRAILLE;
+               else if (p - start == 6 &&
+                               strncasecmp(start, "speech", 6) == 0)
+                       result |= CSS_MEDIA_SPEECH;
+               else if (p - start == 6 &&
+                               strncasecmp(start, "screen", 6) == 0)
+                       result |= CSS_MEDIA_SCREEN;
+               else if (p - start == 5 &&
+                               strncasecmp(start, "print", 5) == 0)
+                       result |= CSS_MEDIA_PRINT;
+               else if (p - start == 5 &&
+                               strncasecmp(start, "aural", 5) == 0)
+                       result |= CSS_MEDIA_AURAL;
+               else if (p - start == 3 &&
+                               strncasecmp(start, "tty", 3) == 0)
+                       result |= CSS_MEDIA_TTY;
+               else if (p - start == 3 &&
+                               strncasecmp(start, "all", 3) == 0)
+                       result |= CSS_MEDIA_ALL;
+               else if (p - start == 2 &&
+                               strncasecmp(start, "tv", 2) == 0)
+                       result |= CSS_MEDIA_TV;
+               else
+                       assert(0 && "Unknown media type");
 
-       /* IDs are case-sensitive in HTML */
-       if (i != node->n_attrs && name == node->attrs[i].value)
-               *match = true;
-       else
-               *match = false;
+               /* Consume whitespace */
+               while (p < end && isspace(*p))
+                       p++;
 
-       return CSS_OK;
-}
+               /* Stop if we've reached the end */
+               if (p == end || *p != ',')
+                       break;
 
-css_error node_has_attribute(void *pw, void *n,
-               const css_qname *qname,
-               bool *match)
-{
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
+               /* Consume comma */
+               p++;
 
-       *match = false;
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
+               /* Consume whitespace */
+               while (p < end && isspace(*p))
+                       p++;
        }
 
-       return CSS_OK;
+       media->type = result;
+
+       *data = p;
+       *len = end - p;
 }
 
-css_error node_has_attribute_equal(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
+static void css__parse_pseudo_list(const char **data, size_t *len, uint32_t 
*element)
 {
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
+       const char *p = *data;
+       const char *end = p + *len;
 
-       *match = false;
+       /* <pseudo> [ ',' <pseudo> ]* */
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
-       }
+       *element = CSS_PSEUDO_ELEMENT_NONE;
 
-       if (*match == true) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, value, match) ==
-                               lwc_error_ok);
-       }
+       while (p < end) {
+               const char *start = p;
 
-       return CSS_OK;
-}
+               /* consume a pseudo */
+               while (isspace(*p) == false && *p != ',')
+                       p++;
 
-css_error node_has_attribute_includes(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
-{
-       node *node = n;
-       uint32_t i;
-       size_t vlen = lwc_string_length(value);
-       UNUSED(pw);
+               /* Pseudo elements */
+               if (p - start == 12 &&
+                               strncasecmp(start, "first-letter", 12) == 0)
+                       *element = CSS_PSEUDO_ELEMENT_FIRST_LETTER;
+               else if (p - start == 10 &&
+                               strncasecmp(start, "first-line", 10) == 0)
+                       *element = CSS_PSEUDO_ELEMENT_FIRST_LINE;
+               else if (p - start == 6 &&
+                               strncasecmp(start, "before", 6) == 0)
+                       *element = CSS_PSEUDO_ELEMENT_BEFORE;
+               else if (p - start == 5 &&
+                               strncasecmp(start, "after", 5) == 0)
+                       *element = CSS_PSEUDO_ELEMENT_AFTER;
+               else
+                       assert(0 && "Unknown pseudo");
 
-       *match = false;
+               /* Consume whitespace */
+               while (p < end && isspace(*p))
+                       p++;
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
+               /* Stop if we've reached the end */
+               if (p == end || *p != ',')
                        break;
-       }
-
-       if (*match == true) {
-               const char *p;
-               const char *start = lwc_string_data(node->attrs[i].value);
-               const char *end = start +
-                               lwc_string_length(node->attrs[i].value);
 
-               *match = false;
-
-               for (p = start; p < end; p++) {
-                       if (*p == ' ') {
-                               if ((size_t) (p - start) == vlen &&
-                                               strncasecmp(start,
-                                                       lwc_string_data(value),
-                                                       vlen) == 0) {
-                                       *match = true;
-                                       break;
-                               }
+               /* Consume comma */
+               p++;
 
-                               start = p + 1;
-                       }
-               }
+               /* Consume whitespace */
+               while (p < end && isspace(*p))
+                       p++;
        }
 
-       return CSS_OK;
+       *data = p;
+       *len = end - p;
 }
 
-css_error node_has_attribute_dashmatch(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
+static void css__parse_tree(line_ctx *ctx, const char *data, size_t len)
 {
-       node *node = n;
-       uint32_t i;
-       size_t vlen = lwc_string_length(value);
-       UNUSED(pw);
+       const char *p = data;
+       const char *end = data + len;
+       size_t left;
 
-       *match = false;
+       /* [ <media_list> <pseudo>? ] ? */
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
-       }
+       ctx->media.type = CSS_MEDIA_ALL;
+       ctx->pseudo_element = CSS_PSEUDO_ELEMENT_NONE;
 
-       if (*match == true) {
-               const char *p;
-               const char *start = lwc_string_data(node->attrs[i].value);
-               const char *end = start +
-                               lwc_string_length(node->attrs[i].value);
+       /* Consume any leading whitespace */
+       while (p < end && isspace(*p))
+               p++;
 
-               *match = false;
+       if (p < end) {
+               left = end - p;
 
-               for (p = start; p < end; p++) {
-                       if (*p == '-') {
-                               if ((size_t) (p - start) == vlen &&
-                                               strncasecmp(start,
-                                                       lwc_string_data(value),
-                                                       vlen) == 0) {
-                                       *match = true;
-                                       break;
-                               }
+               css__parse_media_list(&p, &left, &ctx->media);
 
-                               start = p + 1;
-                       }
-               }
+               end = p + left;
        }
 
-       return CSS_OK;
+       if (p < end) {
+               left = end - p;
+
+               css__parse_pseudo_list(&p, &left, &ctx->pseudo_element);
+       }
 }
 
-css_error node_has_attribute_prefix(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
+static void css__parse_expected(line_ctx *ctx, const char *data, size_t len)
 {
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
-
-       *match = false;
+       while (ctx->expused + len >= ctx->explen) {
+               size_t required = ctx->explen == 0 ? 64 : ctx->explen * 2;
+               char *temp = realloc(ctx->exp, required);
+               if (temp == NULL) {
+                       assert(0 && "No memory for expected output");
+               }
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
+               ctx->exp = temp;
+               ctx->explen = required;
        }
 
-       if (*match == true) {
-               size_t len = lwc_string_length(node->attrs[i].value);
-               const char *data = lwc_string_data(node->attrs[i].value);
-
-               size_t vlen = lwc_string_length(value);
-               const char *vdata = lwc_string_data(value);
-
-               if (len < vlen)
-                       *match = false;
-               else
-                       *match = (strncasecmp(data, vdata, vlen) == 0);
-       }
+       memcpy(ctx->exp + ctx->expused, data, len);
 
-       return CSS_OK;
+       ctx->expused += len;
 }
 
-css_error node_has_attribute_suffix(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
+static void css__parse_tree_data(line_ctx *ctx, const char *data, size_t len)
 {
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
+       const char *p = data;
+       const char *end = data + len;
+       const char *name = NULL;
+       const char *value = NULL;
+       size_t namelen = 0;
+       size_t valuelen = 0;
+       uint32_t depth = 0;
+       bool target = false;
 
-       *match = false;
+       /* ' '{depth+1} [ <element> '*'? | <attr> ]
+        *
+        * <element> ::= [^=*[:space:]]+
+        * <attr>    ::= [^=*[:space:]]+ '=' [^[:space:]]*
+        */
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
+       while (p < end && isspace(*p)) {
+               depth++;
+               p++;
+       }
+       depth--;
+
+       /* Get element/attribute name */
+       name = p;
+       while (p < end && *p != '=' && *p != '*' && isspace(*p) == false) {
+               namelen++;
+               p++;
        }
 
-       if (*match == true) {
-               size_t len = lwc_string_length(node->attrs[i].value);
-               const char *data = lwc_string_data(node->attrs[i].value);
+       /* Skip whitespace */
+       while (p < end && isspace(*p))
+               p++;
 
-               size_t vlen = lwc_string_length(value);
-               const char *vdata = lwc_string_data(value);
+       if (p < end && *p == '=') {
+               /* Attribute value */
+               p++;
 
-               size_t suffix_start = len - vlen;
+               value = p;
 
-               if (len < vlen)
-                       *match = false;
-               else {
-                       *match = (strncasecmp(data + suffix_start,
-                                       vdata, vlen) == 0);
+               while (p < end && isspace(*p) == false) {
+                       valuelen++;
+                       p++;
                }
+       } else if (p < end && *p == '*') {
+               /* Element is target node */
+               target = true;
        }
 
-       return CSS_OK;
-}
-
-css_error node_has_attribute_substring(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
-{
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
-
-       *match = false;
+       if (value == NULL) {
+               /* We have an element, so create it */
+               node *n = malloc(sizeof(node));
+               assert(n != NULL);
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
-       }
+               memset(n, 0, sizeof(node));
 
-       if (*match == true) {
-               size_t len = lwc_string_length(node->attrs[i].value);
-               const char *data = lwc_string_data(node->attrs[i].value);
+               lwc_intern_string(name, namelen, &n->name);
 
-               size_t vlen = lwc_string_length(value);
-               const char *vdata = lwc_string_data(value);
+               /* Insert it into tree */
+               if (ctx->tree == NULL) {
+                       ctx->tree = n;
+               } else {
+                       assert(depth > 0);
+                       assert(depth <= ctx->depth + 1);
 
-               const char *last_start = data + len - vlen;
+                       /* Find node to insert into */
+                       while (depth <= ctx->depth) {
+                               ctx->depth--;
+                               ctx->current = ctx->current->parent;
+                       }
 
-               if (len < vlen)
-                       *match = false;
-               else {
-                       while (data <= last_start) {
-                               if (strncasecmp(data, vdata, vlen) == 0) {
-                                       *match = true;
-                                       break;
-                               }
+                       /* Insert into current node */
+                       if (ctx->current->children == NULL) {
+                               ctx->current->children = n;
+                               ctx->current->last_child = n;
+                       } else {
+                               ctx->current->last_child->next = n;
+                               n->prev = ctx->current->last_child;
 
-                               data++;
+                               ctx->current->last_child = n;
                        }
-
-                       if (data > last_start)
-                               *match = false;
+                       n->parent = ctx->current;
                }
-       }
 
-       return CSS_OK;
-}
+               ctx->current = n;
+               ctx->depth = depth;
 
-css_error node_is_root(void *pw, void *n, bool *match)
-{
-       node *node = n;
-       UNUSED(pw);
+               /* Mark the target, if it's us */
+               if (target)
+                       ctx->target = n;
+       } else {
+               /* New attribute */
+               bool amatch = false;
+               attribute *attr;
+               node *n = ctx->current;
 
-       *match = (node->parent == NULL);
+               attribute *temp = realloc(n->attrs,
+                               (n->n_attrs + 1) * sizeof(attribute));
+               assert(temp != NULL);
 
-       return CSS_OK;
-}
+               n->attrs = temp;
 
-css_error node_count_siblings(void *pw, void *n,
-               bool same_name, bool after, int32_t *count)
-{
-       int32_t cnt = 0;
-       bool match = false;
-       node *node = n;
-       lwc_string *name = node->name;
-       UNUSED(pw);
+               attr = &n->attrs[n->n_attrs];
 
-       if (after) {
-               while (node->next != NULL) {
-                       if (same_name) {
-                               assert(lwc_string_caseless_isequal(
-                                       name, node->next->name, &match) ==
-                                       lwc_error_ok);
+               lwc_intern_string(name, namelen, &attr->name);
+               lwc_intern_string(value, valuelen, &attr->value);
 
-                               if (match)
-                                       cnt++;
-                       } else {
-                               cnt++;
-                       }
+               assert(lwc_string_caseless_isequal(
+                               n->attrs[n->n_attrs].name,
+                               ctx->attr_class, &amatch) == lwc_error_ok);
+               if (amatch == true) {
+                       n->classes = realloc(NULL, sizeof(lwc_string **));
+                       assert(n->classes != NULL);
 
-                       node = node->next;
+                       n->classes[0] = lwc_string_ref(
+                                       n->attrs[n->n_attrs].
+                                       value);
+                       n->n_classes = 1;
                }
-       } else {
-               while (node->prev != NULL) {
-                       if (same_name) {
-                               assert(lwc_string_caseless_isequal(
-                                       name, node->prev->name, &match) ==
-                                       lwc_error_ok);
-
-                               if (match)
-                                       cnt++;
-                       } else {
-                               cnt++;
-                       }
 
-                       node = node->prev;
-               }
+               n->n_attrs++;
        }
-
-       *count = cnt;
-
-       return CSS_OK;
 }
 
-css_error node_is_empty(void *pw, void *n, bool *match)
-{
-       node *node = n;
-       UNUSED(pw);
-
-       *match = (node->children == NULL);
-
-       return CSS_OK;
-}
 
-css_error node_is_link(void *pw, void *n, bool *match)
+static void run_test_select_tree(css_select_ctx *select,
+               node *node, line_ctx *ctx,
+               char *buf, size_t *buflen)
 {
-       node *node = n;
+       css_select_results *sr;
+       struct node *n = NULL;
 
-       UNUSED(pw);
-       UNUSED(node);
+       if (node->parent == NULL) {
+               unit_ctx.root_style = NULL;
+       }
 
-       *match = false;
 
-       return CSS_OK;
-}
+       assert(css_select_style(select, node, &unit_ctx, &ctx->media, NULL,
+                       &select_handler, ctx, &sr) == CSS_OK);
 
-css_error node_is_visited(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       if (node->parent != NULL) {
+               css_computed_style *composed;
+               assert(css_computed_style_compose(
+                               node->parent->sr->styles[ctx->pseudo_element],
+                               sr->styles[ctx->pseudo_element],
+                               &unit_ctx,
+                               &composed) == CSS_OK);
+               css_computed_style_destroy(sr->styles[ctx->pseudo_element]);
+               sr->styles[ctx->pseudo_element] = composed;
+       }
 
-       UNUSED(pw);
-       UNUSED(node);
+       node->sr = sr;
 
-       *match = false;
+       if (node == ctx->target) {
+               dump_computed_style(sr->styles[ctx->pseudo_element],
+                               buf, buflen);
+       }
 
-       return CSS_OK;
+       if (node->parent == NULL) {
+               unit_ctx.root_style = node->sr->styles[ctx->pseudo_element];
+       }
+
+       for (n = node->children; n != NULL; n = n->next) {
+               run_test_select_tree(select, n, ctx, buf, buflen);
+       }
 }
 
-css_error node_is_hover(void *pw, void *n, bool *match)
+static void show_differences(size_t len, const char *exp, const char *res)
 {
-       node *node = n;
-
-       UNUSED(pw);
-       UNUSED(node);
+       const char *pos_exp, *opos_exp;
+       const char *pos_res, *opos_res;
 
-       *match = false;
+       opos_exp = pos_exp = exp;
+       opos_res = pos_res = res;
 
-       return CSS_OK;
+       printf("Line differences:\n");
+       while (pos_exp < exp + len && pos_res < res + len) {
+               if (*pos_exp == '\n' && *pos_res == '\n') {
+                       if (pos_exp - opos_exp != pos_res - opos_res ||
+                                       memcmp(opos_exp, opos_res,
+                                       pos_exp - opos_exp) != 0) {
+                               printf("Expected:\t%.*s\n",
+                                               (int)(pos_exp - opos_exp),
+                                               opos_exp);
+                               printf("  Result:\t%.*s\n",
+                                               (int)(pos_res - opos_res),
+                                               opos_res);
+                               printf("\n");
+                       }
+                       opos_exp = ++pos_exp;
+                       opos_res = ++pos_res;
+               } else if (*pos_exp == '\n') {
+                       pos_res++;
+               } else if (*pos_res == '\n') {
+                       pos_exp++;
+               } else {
+                       pos_exp++;
+                       pos_res++;
+               }
+       }
 }
 
-css_error node_is_active(void *pw, void *n, bool *match)
+static void destroy_tree(node *root)
 {
-       node *node = n;
+       node *n, *p;
+       uint32_t i;
 
-       UNUSED(pw);
-       UNUSED(node);
+       for (n = root->children; n != NULL; n = p) {
+               p = n->next;
 
-       *match = false;
+               destroy_tree(n);
+       }
 
-       return CSS_OK;
-}
+       css_select_results_destroy(root->sr);
 
-css_error node_is_focus(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       for (i = 0; i < root->n_attrs; ++i) {
+               lwc_string_unref(root->attrs[i].name);
+               lwc_string_unref(root->attrs[i].value);
+       }
+       free(root->attrs);
 
-       UNUSED(pw);
-       UNUSED(node);
+       if (root->classes != NULL) {
+               for (i = 0; i < root->n_classes; ++i) {
+                       lwc_string_unref(root->classes[i]);
+               }
+               free(root->classes);
+       }
 
-       *match = false;
+       if (root->libcss_node_data != NULL) {
+               css_libcss_node_data_handler(&select_handler, CSS_NODE_DELETED,
+                               NULL, root, NULL, root->libcss_node_data);
+       }
 
-       return CSS_OK;
+       lwc_string_unref(root->name);
+       free(root);
 }
 
-css_error node_is_enabled(void *pw, void *n, bool *match)
+static void run_test(line_ctx *ctx, const char *exp, size_t explen)
 {
-       node *node = n;
-
-       UNUSED(pw);
-       UNUSED(node);
+       css_select_ctx *select;
+       css_select_results *results;
+       uint32_t i;
+       char *buf;
+       size_t buflen;
+       static int testnum;
 
-       *match = false;
+       UNUSED(exp);
 
-       return CSS_OK;
-}
+       buf = malloc(8192);
+       if (buf == NULL) {
+               assert(0 && "No memory for result data");
+       }
+       buflen = 8192;
 
-css_error node_is_disabled(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       assert(css_select_ctx_create(&select) == CSS_OK);
 
-       UNUSED(pw);
-       UNUSED(node);
+       for (i = 0; i < ctx->n_sheets; i++) {
+               assert(css_select_ctx_append_sheet(select,
+                               ctx->sheets[i].sheet, ctx->sheets[i].origin,
+                               ctx->sheets[i].media) == CSS_OK);
+       }
 
-       *match = false;
+       testnum++;
 
-       return CSS_OK;
-}
+       run_test_select_tree(select, ctx->tree, ctx, buf, &buflen);
 
-css_error node_is_checked(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       results = ctx->target->sr;
+       assert(results->styles[ctx->pseudo_element] != NULL);
 
-       UNUSED(pw);
-       UNUSED(node);
+       if (8192 - buflen != explen || memcmp(buf, exp, explen) != 0) {
+               size_t len = 8192 - buflen < explen ? 8192 - buflen : explen;
+               printf("Expected (%u):\n%.*s\n",
+                               (int) explen, (int) explen, exp);
+               printf("Result (%u):\n%.*s\n", (int) (8192 - buflen),
+                       (int) (8192 - buflen), buf);
 
-       *match = false;
+               show_differences(len, exp, buf);
+               assert(0 && "Result doesn't match expected");
+       }
 
-       return CSS_OK;
-}
+       /* Clean up */
+       css_select_ctx_destroy(select);
+       destroy_tree(ctx->tree);
 
-css_error node_is_target(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       for (i = 0; i < ctx->n_sheets; i++) {
+               css_stylesheet_destroy(ctx->sheets[i].sheet);
+               free(ctx->sheets[i].media);
+       }
 
-       UNUSED(pw);
-       UNUSED(node);
+       ctx->tree = NULL;
+       ctx->current = NULL;
+       ctx->depth = 0;
+       ctx->n_sheets = 0;
+       free(ctx->sheets);
+       ctx->sheets = NULL;
+       ctx->target = NULL;
 
-       *match = false;
+       free(buf);
 
-       return CSS_OK;
+       printf("Test %d: PASS\n", testnum);
 }
 
-css_error node_is_lang(void *pw, void *n,
-               lwc_string *lang,
-               bool *match)
+static bool handle_line(const char *data, size_t datalen, void *pw)
 {
-       node *node = n;
-
-       UNUSED(pw);
-       UNUSED(node);
-       UNUSED(lang);
+       line_ctx *ctx = (line_ctx *) pw;
+       css_error error;
 
-       *match = false;
+       if (data[0] == '#') {
+               if (ctx->intree) {
+                       if (strncasecmp(data+1, "errors", 6) == 0) {
+                               ctx->intree = false;
+                               ctx->insheet = false;
+                               ctx->inerrors = true;
+                               ctx->inexp = false;
+                       } else {
+                               /* Assume start of stylesheet */
+                               css__parse_sheet(ctx, data + 1, datalen - 1);
 
-       return CSS_OK;
-}
+                               ctx->intree = false;
+                               ctx->insheet = true;
+                               ctx->inerrors = false;
+                               ctx->inexp = false;
+                       }
+               } else if (ctx->insheet) {
+                       if (strncasecmp(data+1, "errors", 6) == 0) {
+                               assert(css_stylesheet_data_done(
+                                       ctx->sheets[ctx->n_sheets - 1].sheet)
+                                       == CSS_OK);
 
-css_error node_presentational_hint(void *pw, void *node,
-               uint32_t *nhints, css_hint **hints)
-{
-       UNUSED(pw);
-       UNUSED(node);
+                               ctx->intree = false;
+                               ctx->insheet = false;
+                               ctx->inerrors = true;
+                               ctx->inexp = false;
+                       } else if (strncasecmp(data+1, "ua", 2) == 0 ||
+                                       strncasecmp(data+1, "user", 4) == 0 ||
+                                       strncasecmp(data+1, "author", 6) == 0) {
+                               assert(css_stylesheet_data_done(
+                                       ctx->sheets[ctx->n_sheets - 1].sheet)
+                                       == CSS_OK);
 
-       *nhints = 0;
-       *hints = NULL;
+                               css__parse_sheet(ctx, data + 1, datalen - 1);
+                       } else {
+                               error = css_stylesheet_append_data(
+                                       ctx->sheets[ctx->n_sheets - 1].sheet,
+                                       (const uint8_t *) data,
+                                       datalen);
+                               assert(error == CSS_OK ||
+                                               error == CSS_NEEDDATA);
+                       }
+               } else if (ctx->inerrors) {
+                       ctx->intree = false;
+                       ctx->insheet = false;
+                       ctx->inerrors = false;
+                       ctx->inexp = true;
+               } else if (ctx->inexp) {
+                       /* This marks end of testcase, so run it */
+                       run_test(ctx, ctx->exp, ctx->expused);
 
-       return CSS_OK;
-}
+                       ctx->expused = 0;
 
-css_error ua_default_for_property(void *pw, uint32_t property, css_hint *hint)
-{
-       UNUSED(pw);
+                       ctx->intree = false;
+                       ctx->insheet = false;
+                       ctx->inerrors = false;
+                       ctx->inexp = false;
+               } else {
+                       /* Start state */
+                       if (strncasecmp(data+1, "tree", 4) == 0) {
+                               css__parse_tree(ctx, data + 5, datalen - 5);
 
-       if (property == CSS_PROP_COLOR) {
-               hint->data.color = 0xff000000;
-               hint->status = CSS_COLOR_COLOR;
-       } else if (property == CSS_PROP_FONT_FAMILY) {
-               hint->data.strings = NULL;
-               hint->status = CSS_FONT_FAMILY_SANS_SERIF;
-       } else if (property == CSS_PROP_QUOTES) {
-               /* Not exactly useful :) */
-               hint->data.strings = NULL;
-               hint->status = CSS_QUOTES_NONE;
-       } else if (property == CSS_PROP_VOICE_FAMILY) {
-               /** \todo Fix this when we have voice-family done */
-               hint->data.strings = NULL;
-               hint->status = 0;
+                               ctx->intree = true;
+                               ctx->insheet = false;
+                               ctx->inerrors = false;
+                               ctx->inexp = false;
+                       }
+               }
        } else {
-               return CSS_INVALID;
+               if (ctx->intree) {
+                       /* Not interested in the '|' */
+                       css__parse_tree_data(ctx, data + 1, datalen - 1);
+               } else if (ctx->insheet) {
+                       error = css_stylesheet_append_data(
+                                       ctx->sheets[ctx->n_sheets - 1].sheet,
+                                       (const uint8_t *) data, datalen);
+                       assert(error == CSS_OK || error == CSS_NEEDDATA);
+               } else if (ctx->inexp) {
+                       css__parse_expected(ctx, data, datalen);
+               }
        }
 
-       return CSS_OK;
+       return true;
 }
 
-static css_error set_libcss_node_data(void *pw, void *n,
-               void *libcss_node_data)
+int main(int argc, char **argv)
 {
-       node *node = n;
-       UNUSED(pw);
+       line_ctx ctx;
 
-       node->libcss_node_data = libcss_node_data;
+       if (argc != 2) {
+               printf("Usage: %s <filename>\n", argv[0]);
+               return 1;
+       }
 
-       return CSS_OK;
-}
+       memset(&ctx, 0, sizeof(ctx));
 
-static css_error get_libcss_node_data(void *pw, void *n,
-               void **libcss_node_data)
-{
-       node *node = n;
-       UNUSED(pw);
 
-       /* Pass any node data back to libcss */
-       *libcss_node_data = node->libcss_node_data;
+       lwc_intern_string("class", SLEN("class"), &ctx.attr_class);
+       lwc_intern_string("id", SLEN("id"), &ctx.attr_id);
 
-       return CSS_OK;
-}
+       assert(css__parse_testfile(argv[1], handle_line, &ctx) == true);
+
+       /* and run final test */
+       if (ctx.tree != NULL)
+               run_test(&ctx, ctx.exp, ctx.expused);
+
+       free(ctx.exp);
+
+       lwc_string_unref(ctx.attr_class);
+       lwc_string_unref(ctx.attr_id);
+
+       lwc_iterate_strings(printing_lwc_iterator, NULL);
 
+       assert(fail_because_lwc_leaked == false);
 
+       printf("PASS\n");
+       return 0;
+}


-----------------------------------------------------------------------

Summary of changes:
 test/select.c | 2346 +++++++++++++++++++++++++++------------------------------
 1 file changed, 1124 insertions(+), 1222 deletions(-)

diff --git a/test/select.c b/test/select.c
index c104b38..5bc7856 100644
--- a/test/select.c
+++ b/test/select.c
@@ -70,1607 +70,1509 @@ typedef struct line_ctx {
        lwc_string *attr_id;
 } line_ctx;
 
+static css_error node_name(void *pw, void *n, css_qname *qname)
+{
+       node *node = n;
 
+       UNUSED(pw);
 
+       qname->name = lwc_string_ref(node->name);
 
-static bool handle_line(const char *data, size_t datalen, void *pw);
-static void css__parse_tree(line_ctx *ctx, const char *data, size_t len);
-static void css__parse_tree_data(line_ctx *ctx, const char *data, size_t len);
-static void css__parse_sheet(line_ctx *ctx, const char *data, size_t len);
-static void css__parse_media_list(const char **data, size_t *len, css_media 
*media);
-static void css__parse_pseudo_list(const char **data, size_t *len,
-               uint32_t *element);
-static void css__parse_expected(line_ctx *ctx, const char *data, size_t len);
-static void run_test(line_ctx *ctx, const char *exp, size_t explen);
-static void destroy_tree(node *root);
+       return CSS_OK;
+}
 
-static css_error node_name(void *pw, void *node,
-               css_qname *qname);
 static css_error node_classes(void *pw, void *n,
-               lwc_string ***classes, uint32_t *n_classes);
-static css_error node_id(void *pw, void *node,
-               lwc_string **id);
-static css_error named_ancestor_node(void *pw, void *node,
-               const css_qname *qname,
-               void **ancestor);
-static css_error named_parent_node(void *pw, void *node,
-               const css_qname *qname,
-               void **parent);
-static css_error named_sibling_node(void *pw, void *node,
-               const css_qname *qname,
-               void **sibling);
-static css_error named_generic_sibling_node(void *pw, void *node,
-               const css_qname *qname,
-               void **sibling);
-static css_error parent_node(void *pw, void *node, void **parent);
-static css_error sibling_node(void *pw, void *node, void **sibling);
-static css_error node_has_name(void *pw, void *node,
-               const css_qname *qname,
-               bool *match);
-static css_error node_has_class(void *pw, void *node,
-               lwc_string *name,
-               bool *match);
-static css_error node_has_id(void *pw, void *node,
-               lwc_string *name,
-               bool *match);
-static css_error node_has_attribute(void *pw, void *node,
-               const css_qname *qname,
-               bool *match);
-static css_error node_has_attribute_equal(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_dashmatch(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_includes(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_prefix(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_suffix(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_has_attribute_substring(void *pw, void *node,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match);
-static css_error node_is_root(void *pw, void *node, bool *match);
-static css_error node_count_siblings(void *pw, void *node,
-               bool same_name, bool after, int32_t *count);
-static css_error node_is_empty(void *pw, void *node, bool *match);
-static css_error node_is_link(void *pw, void *node, bool *match);
-static css_error node_is_visited(void *pw, void *node, bool *match);
-static css_error node_is_hover(void *pw, void *node, bool *match);
-static css_error node_is_active(void *pw, void *node, bool *match);
-static css_error node_is_focus(void *pw, void *node, bool *match);
-static css_error node_is_enabled(void *pw, void *node, bool *match);
-static css_error node_is_disabled(void *pw, void *node, bool *match);
-static css_error node_is_checked(void *pw, void *node, bool *match);
-static css_error node_is_target(void *pw, void *node, bool *match);
-static css_error node_is_lang(void *pw, void *node,
-               lwc_string *lang, bool *match);
-static css_error node_presentational_hint(void *pw, void *node,
-               uint32_t *nhints, css_hint **hints);
-static css_error ua_default_for_property(void *pw, uint32_t property,
-               css_hint *hints);
-static css_error set_libcss_node_data(void *pw, void *n,
-               void *libcss_node_data);
-static css_error get_libcss_node_data(void *pw, void *n,
-               void **libcss_node_data);
+               lwc_string ***classes, uint32_t *n_classes)
+{
+       unsigned int i;
+       node *node = n;
+       UNUSED(pw);
 
-static css_unit_ctx unit_ctx = {
-       .font_size_default = 16 * (1 << CSS_RADIX_POINT),
-};
+       *classes = node->classes;
+       *n_classes = node->n_classes;
 
-static css_select_handler select_handler = {
-       CSS_SELECT_HANDLER_VERSION_1,
+       for (i = 0; i < *n_classes; i++)
+               (*classes)[i] = lwc_string_ref(node->classes[i]);
 
-       node_name,
-       node_classes,
-       node_id,
-       named_ancestor_node,
-       named_parent_node,
-       named_sibling_node,
-       named_generic_sibling_node,
-       parent_node,
-       sibling_node,
-       node_has_name,
-       node_has_class,
-       node_has_id,
-       node_has_attribute,
-       node_has_attribute_equal,
-       node_has_attribute_dashmatch,
-       node_has_attribute_includes,
-       node_has_attribute_prefix,
-       node_has_attribute_suffix,
-       node_has_attribute_substring,
-       node_is_root,
-       node_count_siblings,
-       node_is_empty,
-       node_is_link,
-       node_is_visited,
-       node_is_hover,
-       node_is_active,
-       node_is_focus,
-       node_is_enabled,
-       node_is_disabled,
-       node_is_checked,
-       node_is_target,
-       node_is_lang,
-       node_presentational_hint,
-       ua_default_for_property,
+       return CSS_OK;
 
-       set_libcss_node_data,
-       get_libcss_node_data,
-};
+}
 
-static css_error resolve_url(void *pw,
-               const char *base, lwc_string *rel, lwc_string **abs)
+static css_error node_id(void *pw, void *n,
+               lwc_string **id)
+{
+       node *node = n;
+       uint32_t i;
+       line_ctx *lc = pw;
+
+       for (i = 0; i < node->n_attrs; i++) {
+               bool amatch = false;
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, lc->attr_id, &amatch) ==
+                               lwc_error_ok);
+               if (amatch == true)
+                       break;
+       }
+
+       if (i != node->n_attrs)
+               *id = lwc_string_ref(node->attrs[i].value);
+       else
+               *id = NULL;
+
+       return CSS_OK;
+}
+
+static css_error named_ancestor_node(void *pw, void *n,
+               const css_qname *qname,
+               void **ancestor)
 {
+       node *node = n;
        UNUSED(pw);
-       UNUSED(base);
 
-       /* About as useless as possible */
-       *abs = lwc_string_ref(rel);
+       for (node = node->parent; node != NULL; node = node->parent) {
+               bool match = false;
+               assert(lwc_string_caseless_isequal(
+                               qname->name, node->name,
+                               &match) == lwc_error_ok);
+               if (match == true)
+                       break;
+       }
+
+       *ancestor = (void *) node;
 
        return CSS_OK;
 }
 
-static bool fail_because_lwc_leaked = false;
+static css_error named_parent_node(void *pw, void *n,
+               const css_qname *qname,
+               void **parent)
+{
+       node *node = n;
+       UNUSED(pw);
 
-static void
-printing_lwc_iterator(lwc_string *str, void *pw)
+       *parent = NULL;
+       if (node->parent != NULL) {
+               bool match = false;
+               assert(lwc_string_caseless_isequal(
+                               qname->name, node->parent->name, &match) ==
+                               lwc_error_ok);
+               if (match == true)
+                       *parent = (void *) node->parent;
+       }
+
+       return CSS_OK;
+}
+
+static css_error named_sibling_node(void *pw, void *n,
+               const css_qname *qname,
+               void **sibling)
 {
+       node *node = n;
        UNUSED(pw);
 
-       printf(" DICT: %*s\n", (int)(lwc_string_length(str)), 
lwc_string_data(str));
-       fail_because_lwc_leaked = true;
+       *sibling = NULL;
+       if (node->prev != NULL) {
+               bool match = false;
+               assert(lwc_string_caseless_isequal(
+                               qname->name, node->prev->name, &match) ==
+                               lwc_error_ok);
+               if (match == true)
+                       *sibling = (void *) node->prev;
+       }
+
+       return CSS_OK;
 }
 
-int main(int argc, char **argv)
+static css_error named_generic_sibling_node(void *pw, void *n,
+               const css_qname *qname,
+               void **sibling)
 {
-       line_ctx ctx;
+       node *node = n;
+       UNUSED(pw);
 
-       if (argc != 2) {
-               printf("Usage: %s <filename>\n", argv[0]);
-               return 1;
+       for (node = node->prev; node != NULL; node = node->prev) {
+               bool match = false;
+               assert(lwc_string_caseless_isequal(
+                               qname->name, node->name,
+                               &match) == lwc_error_ok);
+               if (match == true)
+                       break;
        }
 
-       memset(&ctx, 0, sizeof(ctx));
+       *sibling = (void *) node;
 
+       return CSS_OK;
+}
 
-       lwc_intern_string("class", SLEN("class"), &ctx.attr_class);
-       lwc_intern_string("id", SLEN("id"), &ctx.attr_id);
+static css_error parent_node(void *pw, void *n, void **parent)
+{
+       node *node = n;
 
-       assert(css__parse_testfile(argv[1], handle_line, &ctx) == true);
+       UNUSED(pw);
 
-       /* and run final test */
-       if (ctx.tree != NULL)
-               run_test(&ctx, ctx.exp, ctx.expused);
+       *parent = (void *) node->parent;
 
-       free(ctx.exp);
+       return CSS_OK;
+}
 
-       lwc_string_unref(ctx.attr_class);
-       lwc_string_unref(ctx.attr_id);
+static css_error sibling_node(void *pw, void *n, void **sibling)
+{
+       node *node = n;
 
-       lwc_iterate_strings(printing_lwc_iterator, NULL);
+       UNUSED(pw);
 
-       assert(fail_because_lwc_leaked == false);
+       *sibling = (void *) node->prev;
 
-       printf("PASS\n");
-       return 0;
+       return CSS_OK;
 }
 
-bool handle_line(const char *data, size_t datalen, void *pw)
+static css_error node_has_name(void *pw, void *n,
+               const css_qname *qname,
+               bool *match)
 {
-       line_ctx *ctx = (line_ctx *) pw;
-       css_error error;
+       node *node = n;
+       UNUSED(pw);
 
-       if (data[0] == '#') {
-               if (ctx->intree) {
-                       if (strncasecmp(data+1, "errors", 6) == 0) {
-                               ctx->intree = false;
-                               ctx->insheet = false;
-                               ctx->inerrors = true;
-                               ctx->inexp = false;
-                       } else {
-                               /* Assume start of stylesheet */
-                               css__parse_sheet(ctx, data + 1, datalen - 1);
+       if (lwc_string_length(qname->name) == 1 &&
+                       lwc_string_data(qname->name)[0] == '*')
+               *match = true;
+       else
+               assert(lwc_string_caseless_isequal(node->name,
+                       qname->name, match) == lwc_error_ok);
 
-                               ctx->intree = false;
-                               ctx->insheet = true;
-                               ctx->inerrors = false;
-                               ctx->inexp = false;
-                       }
-               } else if (ctx->insheet) {
-                       if (strncasecmp(data+1, "errors", 6) == 0) {
-                               assert(css_stylesheet_data_done(
-                                       ctx->sheets[ctx->n_sheets - 1].sheet)
-                                       == CSS_OK);
+       return CSS_OK;
+}
 
-                               ctx->intree = false;
-                               ctx->insheet = false;
-                               ctx->inerrors = true;
-                               ctx->inexp = false;
-                       } else if (strncasecmp(data+1, "ua", 2) == 0 ||
-                                       strncasecmp(data+1, "user", 4) == 0 ||
-                                       strncasecmp(data+1, "author", 6) == 0) {
-                               assert(css_stylesheet_data_done(
-                                       ctx->sheets[ctx->n_sheets - 1].sheet)
-                                       == CSS_OK);
+static css_error node_has_class(void *pw, void *n,
+               lwc_string *name,
+               bool *match)
+{
+       node *node = n;
+       uint32_t i;
+       line_ctx *ctx = pw;
 
-                               css__parse_sheet(ctx, data + 1, datalen - 1);
-                       } else {
-                               error = css_stylesheet_append_data(
-                                       ctx->sheets[ctx->n_sheets - 1].sheet,
-                                       (const uint8_t *) data,
-                                       datalen);
-                               assert(error == CSS_OK ||
-                                               error == CSS_NEEDDATA);
-                       }
-               } else if (ctx->inerrors) {
-                       ctx->intree = false;
-                       ctx->insheet = false;
-                       ctx->inerrors = false;
-                       ctx->inexp = true;
-               } else if (ctx->inexp) {
-                       /* This marks end of testcase, so run it */
-                       run_test(ctx, ctx->exp, ctx->expused);
-
-                       ctx->expused = 0;
-
-                       ctx->intree = false;
-                       ctx->insheet = false;
-                       ctx->inerrors = false;
-                       ctx->inexp = false;
-               } else {
-                       /* Start state */
-                       if (strncasecmp(data+1, "tree", 4) == 0) {
-                               css__parse_tree(ctx, data + 5, datalen - 5);
-
-                               ctx->intree = true;
-                               ctx->insheet = false;
-                               ctx->inerrors = false;
-                               ctx->inexp = false;
-                       }
-               }
-       } else {
-               if (ctx->intree) {
-                       /* Not interested in the '|' */
-                       css__parse_tree_data(ctx, data + 1, datalen - 1);
-               } else if (ctx->insheet) {
-                       error = css_stylesheet_append_data(
-                                       ctx->sheets[ctx->n_sheets - 1].sheet,
-                                       (const uint8_t *) data, datalen);
-                       assert(error == CSS_OK || error == CSS_NEEDDATA);
-               } else if (ctx->inexp) {
-                       css__parse_expected(ctx, data, datalen);
-               }
+       for (i = 0; i < node->n_attrs; i++) {
+               bool amatch = false;
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, ctx->attr_class,
+                               &amatch) == lwc_error_ok);
+               if (amatch == true)
+                       break;
        }
 
-       return true;
+       /* Classes are case-sensitive in HTML */
+       if (i != node->n_attrs && name == node->attrs[i].value)
+               *match = true;
+       else
+               *match = false;
+
+       return CSS_OK;
 }
 
-void css__parse_tree(line_ctx *ctx, const char *data, size_t len)
+static css_error node_has_id(void *pw, void *n,
+               lwc_string *name,
+               bool *match)
 {
-       const char *p = data;
-       const char *end = data + len;
-       size_t left;
-
-       /* [ <media_list> <pseudo>? ] ? */
-
-       ctx->media.type = CSS_MEDIA_ALL;
-       ctx->pseudo_element = CSS_PSEUDO_ELEMENT_NONE;
-
-       /* Consume any leading whitespace */
-       while (p < end && isspace(*p))
-               p++;
-
-       if (p < end) {
-               left = end - p;
-
-               css__parse_media_list(&p, &left, &ctx->media);
+       node *node = n;
+       uint32_t i;
+       line_ctx *ctx = pw;
 
-               end = p + left;
+       for (i = 0; i < node->n_attrs; i++) {
+               bool amatch = false;
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, ctx->attr_id, &amatch) ==
+                               lwc_error_ok);
+               if (amatch == true)
+                       break;
        }
 
-       if (p < end) {
-               left = end - p;
+       /* IDs are case-sensitive in HTML */
+       if (i != node->n_attrs && name == node->attrs[i].value)
+               *match = true;
+       else
+               *match = false;
 
-               css__parse_pseudo_list(&p, &left, &ctx->pseudo_element);
-       }
+       return CSS_OK;
 }
 
-void css__parse_tree_data(line_ctx *ctx, const char *data, size_t len)
+static css_error node_has_attribute(void *pw, void *n,
+               const css_qname *qname,
+               bool *match)
 {
-       const char *p = data;
-       const char *end = data + len;
-       const char *name = NULL;
-       const char *value = NULL;
-       size_t namelen = 0;
-       size_t valuelen = 0;
-       uint32_t depth = 0;
-       bool target = false;
-
-       /* ' '{depth+1} [ <element> '*'? | <attr> ]
-        *
-        * <element> ::= [^=*[:space:]]+
-        * <attr>    ::= [^=*[:space:]]+ '=' [^[:space:]]*
-        */
-
-       while (p < end && isspace(*p)) {
-               depth++;
-               p++;
-       }
-       depth--;
-
-       /* Get element/attribute name */
-       name = p;
-       while (p < end && *p != '=' && *p != '*' && isspace(*p) == false) {
-               namelen++;
-               p++;
-       }
-
-       /* Skip whitespace */
-       while (p < end && isspace(*p))
-               p++;
-
-       if (p < end && *p == '=') {
-               /* Attribute value */
-               p++;
-
-               value = p;
-
-               while (p < end && isspace(*p) == false) {
-                       valuelen++;
-                       p++;
-               }
-       } else if (p < end && *p == '*') {
-               /* Element is target node */
-               target = true;
-       }
-
-       if (value == NULL) {
-               /* We have an element, so create it */
-               node *n = malloc(sizeof(node));
-               assert(n != NULL);
-
-               memset(n, 0, sizeof(node));
-
-               lwc_intern_string(name, namelen, &n->name);
-
-               /* Insert it into tree */
-               if (ctx->tree == NULL) {
-                       ctx->tree = n;
-               } else {
-                       assert(depth > 0);
-                       assert(depth <= ctx->depth + 1);
-
-                       /* Find node to insert into */
-                       while (depth <= ctx->depth) {
-                               ctx->depth--;
-                               ctx->current = ctx->current->parent;
-                       }
-
-                       /* Insert into current node */
-                       if (ctx->current->children == NULL) {
-                               ctx->current->children = n;
-                               ctx->current->last_child = n;
-                       } else {
-                               ctx->current->last_child->next = n;
-                               n->prev = ctx->current->last_child;
-
-                               ctx->current->last_child = n;
-                       }
-                       n->parent = ctx->current;
-               }
-
-               ctx->current = n;
-               ctx->depth = depth;
-
-               /* Mark the target, if it's us */
-               if (target)
-                       ctx->target = n;
-       } else {
-               /* New attribute */
-               bool amatch = false;
-               attribute *attr;
-               node *n = ctx->current;
-
-               attribute *temp = realloc(n->attrs,
-                               (n->n_attrs + 1) * sizeof(attribute));
-               assert(temp != NULL);
-
-               n->attrs = temp;
-
-               attr = &n->attrs[n->n_attrs];
-
-               lwc_intern_string(name, namelen, &attr->name);
-               lwc_intern_string(value, valuelen, &attr->value);
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
 
+       *match = false;
+       for (i = 0; i < node->n_attrs; i++) {
                assert(lwc_string_caseless_isequal(
-                               n->attrs[n->n_attrs].name,
-                               ctx->attr_class, &amatch) == lwc_error_ok);
-               if (amatch == true) {
-                       n->classes = realloc(NULL, sizeof(lwc_string **));
-                       assert(n->classes != NULL);
-
-                       n->classes[0] = lwc_string_ref(
-                                       n->attrs[n->n_attrs].
-                                       value);
-                       n->n_classes = 1;
-               }
-
-               n->n_attrs++;
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
        }
+
+       return CSS_OK;
 }
 
-static css_error css_font_resolution_func(void *pw, lwc_string *name,
-               css_system_font *system_font)
+static css_error node_has_attribute_equal(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
 {
-       lwc_error err;
-
-       if (system_font == NULL) {
-               return CSS_BADPARM;
-       }
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
 
-       (void)(pw);
+       *match = false;
 
-       if (strncmp(lwc_string_data(name), "special-system-font",
-                       lwc_string_length(name)) != 0) {
-               return CSS_INVALID;
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
        }
 
-       system_font->style = CSS_FONT_STYLE_NORMAL;
-       system_font->variant = CSS_FONT_VARIANT_NORMAL;
-       system_font->weight = CSS_FONT_WEIGHT_NORMAL;
-       system_font->size.size = INTTOFIX(22);
-       system_font->size.unit = CSS_UNIT_PT;
-       system_font->line_height.size = INTTOFIX(33);
-       system_font->line_height.unit = CSS_UNIT_EM;
-       err = lwc_intern_string("special-system-font",
-                       strlen("special-system-font"),
-                       &system_font->family);
-       if (err != lwc_error_ok) {
-               return CSS_NOMEM;
+       if (*match == true) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, value, match) ==
+                               lwc_error_ok);
        }
 
        return CSS_OK;
 }
 
-void css__parse_sheet(line_ctx *ctx, const char *data, size_t len)
+static css_error node_has_attribute_includes(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
 {
-       css_stylesheet_params params;
-       const char *p;
-       const char *end = data + len;
-       css_origin origin = CSS_ORIGIN_AUTHOR;
-       css_stylesheet *sheet;
-       sheet_ctx *temp;
-       char *media = NULL;
+       node *node = n;
+       uint32_t i;
+       size_t vlen = lwc_string_length(value);
+       UNUSED(pw);
 
-       /* <origin> <media_list>? */
+       *match = false;
 
-       /* Find end of origin */
-       for (p = data; p < end; p++) {
-               if (isspace(*p))
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
                        break;
        }
 
-       if (p - data == 6 && strncasecmp(data, "author", 6) == 0)
-               origin = CSS_ORIGIN_AUTHOR;
-       else if (p - data == 4 && strncasecmp(data, "user", 4) == 0)
-               origin = CSS_ORIGIN_USER;
-       else if (p - data == 2 && strncasecmp(data, "ua", 2) == 0)
-               origin = CSS_ORIGIN_UA;
-       else
-               assert(0 && "Unknown stylesheet origin");
-
-       /* Skip any whitespace */
-       while (p < end && isspace(*p))
-               p++;
-
-       assert(end >= p);
-       media = malloc(end - p + 1);
-       assert(media != NULL);
-       memcpy(media, p, end - p);
-       media[end - p] = '\0';
-
-       params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
-       params.level = CSS_LEVEL_21;
-       params.charset = "UTF-8";
-       params.url = "foo";
-       params.title = "foo";
-       params.allow_quirks = false;
-       params.inline_style = false;
-       params.resolve = resolve_url;
-       params.resolve_pw = NULL;
-       params.import = NULL;
-       params.import_pw = NULL;
-       params.color = NULL;
-       params.color_pw = NULL;
-       params.font = css_font_resolution_func;
-       params.font_pw = NULL;
-
-       /** \todo How are we going to handle @import? */
-       assert(css_stylesheet_create(&params, &sheet) == CSS_OK);
-
-       /* Extend array of sheets and append new sheet to it */
-       temp = realloc(ctx->sheets,
-                       (ctx->n_sheets + 1) * sizeof(sheet_ctx));
-       assert(temp != NULL);
-
-       ctx->sheets = temp;
-
-       ctx->sheets[ctx->n_sheets].sheet = sheet;
-       ctx->sheets[ctx->n_sheets].origin = origin;
-       ctx->sheets[ctx->n_sheets].media = media;
-
-       ctx->n_sheets++;
-}
-
-void css__parse_media_list(const char **data, size_t *len, css_media *media)
-{
-       const char *p = *data;
-       const char *end = p + *len;
-       uint64_t result = 0;
-
-       /* <medium> [ ',' <medium> ]* */
-
-       while (p < end) {
-               const char *start = p;
+       if (*match == true) {
+               const char *p;
+               const char *start = lwc_string_data(node->attrs[i].value);
+               const char *end = start +
+                               lwc_string_length(node->attrs[i].value);
 
-               /* consume a medium */
-               while (isspace(*p) == false && *p != ',')
-                       p++;
+               *match = false;
 
-               if (p - start == 10 &&
-                               strncasecmp(start, "projection", 10) == 0)
-                       result |= CSS_MEDIA_PROJECTION;
-               else if (p - start == 8 &&
-                               strncasecmp(start, "handheld", 8) == 0)
-                       result |= CSS_MEDIA_HANDHELD;
-               else if (p - start == 8 &&
-                               strncasecmp(start, "embossed", 8) == 0)
-                       result |= CSS_MEDIA_EMBOSSED;
-               else if (p - start == 7 &&
-                               strncasecmp(start, "braille", 7) == 0)
-                       result |= CSS_MEDIA_BRAILLE;
-               else if (p - start == 6 &&
-                               strncasecmp(start, "speech", 6) == 0)
-                       result |= CSS_MEDIA_SPEECH;
-               else if (p - start == 6 &&
-                               strncasecmp(start, "screen", 6) == 0)
-                       result |= CSS_MEDIA_SCREEN;
-               else if (p - start == 5 &&
-                               strncasecmp(start, "print", 5) == 0)
-                       result |= CSS_MEDIA_PRINT;
-               else if (p - start == 5 &&
-                               strncasecmp(start, "aural", 5) == 0)
-                       result |= CSS_MEDIA_AURAL;
-               else if (p - start == 3 &&
-                               strncasecmp(start, "tty", 3) == 0)
-                       result |= CSS_MEDIA_TTY;
-               else if (p - start == 3 &&
-                               strncasecmp(start, "all", 3) == 0)
-                       result |= CSS_MEDIA_ALL;
-               else if (p - start == 2 &&
-                               strncasecmp(start, "tv", 2) == 0)
-                       result |= CSS_MEDIA_TV;
-               else
-                       assert(0 && "Unknown media type");
+               for (p = start; p < end; p++) {
+                       if (*p == ' ') {
+                               if ((size_t) (p - start) == vlen &&
+                                               strncasecmp(start,
+                                                       lwc_string_data(value),
+                                                       vlen) == 0) {
+                                       *match = true;
+                                       break;
+                               }
 
-               /* Consume whitespace */
-               while (p < end && isspace(*p))
-                       p++;
+                               start = p + 1;
+                       }
+               }
+       }
 
-               /* Stop if we've reached the end */
-               if (p == end || *p != ',')
-                       break;
+       return CSS_OK;
+}
 
-               /* Consume comma */
-               p++;
+static css_error node_has_attribute_dashmatch(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
+{
+       node *node = n;
+       uint32_t i;
+       size_t vlen = lwc_string_length(value);
+       UNUSED(pw);
 
-               /* Consume whitespace */
-               while (p < end && isspace(*p))
-                       p++;
+       *match = false;
+
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
        }
 
-       media->type = result;
+       if (*match == true) {
+               const char *p;
+               const char *start = lwc_string_data(node->attrs[i].value);
+               const char *end = start +
+                               lwc_string_length(node->attrs[i].value);
 
-       *data = p;
-       *len = end - p;
+               *match = false;
+
+               for (p = start; p < end; p++) {
+                       if (*p == '-') {
+                               if ((size_t) (p - start) == vlen &&
+                                               strncasecmp(start,
+                                                       lwc_string_data(value),
+                                                       vlen) == 0) {
+                                       *match = true;
+                                       break;
+                               }
+
+                               start = p + 1;
+                       }
+               }
+       }
+
+       return CSS_OK;
 }
 
-void css__parse_pseudo_list(const char **data, size_t *len, uint32_t *element)
+static css_error node_has_attribute_prefix(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
 {
-       const char *p = *data;
-       const char *end = p + *len;
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
 
-       /* <pseudo> [ ',' <pseudo> ]* */
+       *match = false;
 
-       *element = CSS_PSEUDO_ELEMENT_NONE;
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
+       }
 
-       while (p < end) {
-               const char *start = p;
+       if (*match == true) {
+               size_t len = lwc_string_length(node->attrs[i].value);
+               const char *data = lwc_string_data(node->attrs[i].value);
 
-               /* consume a pseudo */
-               while (isspace(*p) == false && *p != ',')
-                       p++;
+               size_t vlen = lwc_string_length(value);
+               const char *vdata = lwc_string_data(value);
 
-               /* Pseudo elements */
-               if (p - start == 12 &&
-                               strncasecmp(start, "first-letter", 12) == 0)
-                       *element = CSS_PSEUDO_ELEMENT_FIRST_LETTER;
-               else if (p - start == 10 &&
-                               strncasecmp(start, "first-line", 10) == 0)
-                       *element = CSS_PSEUDO_ELEMENT_FIRST_LINE;
-               else if (p - start == 6 &&
-                               strncasecmp(start, "before", 6) == 0)
-                       *element = CSS_PSEUDO_ELEMENT_BEFORE;
-               else if (p - start == 5 &&
-                               strncasecmp(start, "after", 5) == 0)
-                       *element = CSS_PSEUDO_ELEMENT_AFTER;
+               if (len < vlen)
+                       *match = false;
                else
-                       assert(0 && "Unknown pseudo");
+                       *match = (strncasecmp(data, vdata, vlen) == 0);
+       }
 
-               /* Consume whitespace */
-               while (p < end && isspace(*p))
-                       p++;
+       return CSS_OK;
+}
 
-               /* Stop if we've reached the end */
-               if (p == end || *p != ',')
+static css_error node_has_attribute_suffix(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
+{
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
+
+       *match = false;
+
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
                        break;
+       }
 
-               /* Consume comma */
-               p++;
+       if (*match == true) {
+               size_t len = lwc_string_length(node->attrs[i].value);
+               const char *data = lwc_string_data(node->attrs[i].value);
 
-               /* Consume whitespace */
-               while (p < end && isspace(*p))
-                       p++;
+               size_t vlen = lwc_string_length(value);
+               const char *vdata = lwc_string_data(value);
+
+               size_t suffix_start = len - vlen;
+
+               if (len < vlen)
+                       *match = false;
+               else {
+                       *match = (strncasecmp(data + suffix_start,
+                                       vdata, vlen) == 0);
+               }
        }
 
-       *data = p;
-       *len = end - p;
+       return CSS_OK;
 }
 
-void css__parse_expected(line_ctx *ctx, const char *data, size_t len)
+static css_error node_has_attribute_substring(void *pw, void *n,
+               const css_qname *qname,
+               lwc_string *value,
+               bool *match)
 {
-       while (ctx->expused + len >= ctx->explen) {
-               size_t required = ctx->explen == 0 ? 64 : ctx->explen * 2;
-               char *temp = realloc(ctx->exp, required);
-               if (temp == NULL) {
-                       assert(0 && "No memory for expected output");
-               }
+       node *node = n;
+       uint32_t i;
+       UNUSED(pw);
 
-               ctx->exp = temp;
-               ctx->explen = required;
+       *match = false;
+
+       for (i = 0; i < node->n_attrs; i++) {
+               assert(lwc_string_caseless_isequal(
+                               node->attrs[i].name, qname->name, match) ==
+                               lwc_error_ok);
+               if (*match == true)
+                       break;
        }
 
-       memcpy(ctx->exp + ctx->expused, data, len);
+       if (*match == true) {
+               size_t len = lwc_string_length(node->attrs[i].value);
+               const char *data = lwc_string_data(node->attrs[i].value);
 
-       ctx->expused += len;
-}
+               size_t vlen = lwc_string_length(value);
+               const char *vdata = lwc_string_data(value);
 
-static void show_differences(size_t len, const char *exp, const char *res)
-{
-       const char *pos_exp, *opos_exp;
-       const char *pos_res, *opos_res;
+               const char *last_start = data + len - vlen;
 
-       opos_exp = pos_exp = exp;
-       opos_res = pos_res = res;
+               if (len < vlen)
+                       *match = false;
+               else {
+                       while (data <= last_start) {
+                               if (strncasecmp(data, vdata, vlen) == 0) {
+                                       *match = true;
+                                       break;
+                               }
 
-       printf("Line differences:\n");
-       while (pos_exp < exp + len && pos_res < res + len) {
-               if (*pos_exp == '\n' && *pos_res == '\n') {
-                       if (pos_exp - opos_exp != pos_res - opos_res ||
-                                       memcmp(opos_exp, opos_res,
-                                       pos_exp - opos_exp) != 0) {
-                               printf("Expected:\t%.*s\n",
-                                               (int)(pos_exp - opos_exp),
-                                               opos_exp);
-                               printf("  Result:\t%.*s\n",
-                                               (int)(pos_res - opos_res),
-                                               opos_res);
-                               printf("\n");
+                               data++;
                        }
-                       opos_exp = ++pos_exp;
-                       opos_res = ++pos_res;
-               } else if (*pos_exp == '\n') {
-                       pos_res++;
-               } else if (*pos_res == '\n') {
-                       pos_exp++;
-               } else {
-                       pos_exp++;
-                       pos_res++;
+
+                       if (data > last_start)
+                               *match = false;
                }
        }
+
+       return CSS_OK;
 }
 
+static css_error node_is_root(void *pw, void *n, bool *match)
+{
+       node *node = n;
+       UNUSED(pw);
 
-static void run_test_select_tree(css_select_ctx *select,
-               node *node, line_ctx *ctx,
-               char *buf, size_t *buflen)
+       *match = (node->parent == NULL);
+
+       return CSS_OK;
+}
+
+static css_error node_count_siblings(void *pw, void *n,
+               bool same_name, bool after, int32_t *count)
 {
-       css_select_results *sr;
-       struct node *n = NULL;
+       int32_t cnt = 0;
+       bool match = false;
+       node *node = n;
+       lwc_string *name = node->name;
+       UNUSED(pw);
 
-       if (node->parent == NULL) {
-               unit_ctx.root_style = NULL;
-       }
+       if (after) {
+               while (node->next != NULL) {
+                       if (same_name) {
+                               assert(lwc_string_caseless_isequal(
+                                       name, node->next->name, &match) ==
+                                       lwc_error_ok);
+
+                               if (match)
+                                       cnt++;
+                       } else {
+                               cnt++;
+                       }
 
+                       node = node->next;
+               }
+       } else {
+               while (node->prev != NULL) {
+                       if (same_name) {
+                               assert(lwc_string_caseless_isequal(
+                                       name, node->prev->name, &match) ==
+                                       lwc_error_ok);
 
-       assert(css_select_style(select, node, &unit_ctx, &ctx->media, NULL,
-                       &select_handler, ctx, &sr) == CSS_OK);
+                               if (match)
+                                       cnt++;
+                       } else {
+                               cnt++;
+                       }
 
-       if (node->parent != NULL) {
-               css_computed_style *composed;
-               assert(css_computed_style_compose(
-                               node->parent->sr->styles[ctx->pseudo_element],
-                               sr->styles[ctx->pseudo_element],
-                               &unit_ctx,
-                               &composed) == CSS_OK);
-               css_computed_style_destroy(sr->styles[ctx->pseudo_element]);
-               sr->styles[ctx->pseudo_element] = composed;
+                       node = node->prev;
+               }
        }
 
-       node->sr = sr;
+       *count = cnt;
 
-       if (node == ctx->target) {
-               dump_computed_style(sr->styles[ctx->pseudo_element],
-                               buf, buflen);
-       }
+       return CSS_OK;
+}
 
-       if (node->parent == NULL) {
-               unit_ctx.root_style = node->sr->styles[ctx->pseudo_element];
-       }
+static css_error node_is_empty(void *pw, void *n, bool *match)
+{
+       node *node = n;
+       UNUSED(pw);
 
-       for (n = node->children; n != NULL; n = n->next) {
-               run_test_select_tree(select, n, ctx, buf, buflen);
-       }
-}
+       *match = (node->children == NULL);
 
+       return CSS_OK;
+}
 
-void run_test(line_ctx *ctx, const char *exp, size_t explen)
+static css_error node_is_link(void *pw, void *n, bool *match)
 {
-       css_select_ctx *select;
-       css_select_results *results;
-       uint32_t i;
-       char *buf;
-       size_t buflen;
-       static int testnum;
+       node *node = n;
 
-       UNUSED(exp);
+       UNUSED(pw);
+       UNUSED(node);
 
-       buf = malloc(8192);
-       if (buf == NULL) {
-               assert(0 && "No memory for result data");
-       }
-       buflen = 8192;
+       *match = false;
 
-       assert(css_select_ctx_create(&select) == CSS_OK);
+       return CSS_OK;
+}
 
-       for (i = 0; i < ctx->n_sheets; i++) {
-               assert(css_select_ctx_append_sheet(select,
-                               ctx->sheets[i].sheet, ctx->sheets[i].origin,
-                               ctx->sheets[i].media) == CSS_OK);
-       }
+static css_error node_is_visited(void *pw, void *n, bool *match)
+{
+       node *node = n;
 
-       testnum++;
+       UNUSED(pw);
+       UNUSED(node);
 
-       run_test_select_tree(select, ctx->tree, ctx, buf, &buflen);
+       *match = false;
 
-       results = ctx->target->sr;
-       assert(results->styles[ctx->pseudo_element] != NULL);
+       return CSS_OK;
+}
 
-       if (8192 - buflen != explen || memcmp(buf, exp, explen) != 0) {
-               size_t len = 8192 - buflen < explen ? 8192 - buflen : explen;
-               printf("Expected (%u):\n%.*s\n",
-                               (int) explen, (int) explen, exp);
-               printf("Result (%u):\n%.*s\n", (int) (8192 - buflen),
-                       (int) (8192 - buflen), buf);
+static css_error node_is_hover(void *pw, void *n, bool *match)
+{
+       node *node = n;
 
-               show_differences(len, exp, buf);
-               assert(0 && "Result doesn't match expected");
-       }
+       UNUSED(pw);
+       UNUSED(node);
 
-       /* Clean up */
-       css_select_ctx_destroy(select);
-       destroy_tree(ctx->tree);
+       *match = false;
 
-       for (i = 0; i < ctx->n_sheets; i++) {
-               css_stylesheet_destroy(ctx->sheets[i].sheet);
-               free(ctx->sheets[i].media);
-       }
+       return CSS_OK;
+}
 
-       ctx->tree = NULL;
-       ctx->current = NULL;
-       ctx->depth = 0;
-       ctx->n_sheets = 0;
-       free(ctx->sheets);
-       ctx->sheets = NULL;
-       ctx->target = NULL;
+static css_error node_is_active(void *pw, void *n, bool *match)
+{
+       node *node = n;
 
-       free(buf);
+       UNUSED(pw);
+       UNUSED(node);
 
-       printf("Test %d: PASS\n", testnum);
+       *match = false;
+
+       return CSS_OK;
 }
 
-void destroy_tree(node *root)
+static css_error node_is_focus(void *pw, void *n, bool *match)
 {
-       node *n, *p;
-       uint32_t i;
+       node *node = n;
 
-       for (n = root->children; n != NULL; n = p) {
-               p = n->next;
+       UNUSED(pw);
+       UNUSED(node);
 
-               destroy_tree(n);
-       }
+       *match = false;
 
-       css_select_results_destroy(root->sr);
+       return CSS_OK;
+}
 
-       for (i = 0; i < root->n_attrs; ++i) {
-               lwc_string_unref(root->attrs[i].name);
-               lwc_string_unref(root->attrs[i].value);
-       }
-       free(root->attrs);
+static css_error node_is_enabled(void *pw, void *n, bool *match)
+{
+       node *node = n;
 
-       if (root->classes != NULL) {
-               for (i = 0; i < root->n_classes; ++i) {
-                       lwc_string_unref(root->classes[i]);
-               }
-               free(root->classes);
-       }
+       UNUSED(pw);
+       UNUSED(node);
 
-       if (root->libcss_node_data != NULL) {
-               css_libcss_node_data_handler(&select_handler, CSS_NODE_DELETED,
-                               NULL, root, NULL, root->libcss_node_data);
-       }
+       *match = false;
 
-       lwc_string_unref(root->name);
-       free(root);
+       return CSS_OK;
 }
 
-
-css_error node_name(void *pw, void *n, css_qname *qname)
+static css_error node_is_disabled(void *pw, void *n, bool *match)
 {
        node *node = n;
 
        UNUSED(pw);
+       UNUSED(node);
 
-       qname->name = lwc_string_ref(node->name);
+       *match = false;
 
        return CSS_OK;
 }
 
-static css_error node_classes(void *pw, void *n,
-               lwc_string ***classes, uint32_t *n_classes)
+static css_error node_is_checked(void *pw, void *n, bool *match)
 {
-       unsigned int i;
        node *node = n;
-       UNUSED(pw);
 
-       *classes = node->classes;
-       *n_classes = node->n_classes;
+       UNUSED(pw);
+       UNUSED(node);
 
-       for (i = 0; i < *n_classes; i++)
-               (*classes)[i] = lwc_string_ref(node->classes[i]);
+       *match = false;
 
        return CSS_OK;
-
 }
 
-css_error node_id(void *pw, void *n,
-               lwc_string **id)
+static css_error node_is_target(void *pw, void *n, bool *match)
 {
        node *node = n;
-       uint32_t i;
-       line_ctx *lc = pw;
 
-       for (i = 0; i < node->n_attrs; i++) {
-               bool amatch = false;
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, lc->attr_id, &amatch) ==
-                               lwc_error_ok);
-               if (amatch == true)
-                       break;
-       }
+       UNUSED(pw);
+       UNUSED(node);
 
-       if (i != node->n_attrs)
-               *id = lwc_string_ref(node->attrs[i].value);
-       else
-               *id = NULL;
+       *match = false;
 
        return CSS_OK;
 }
 
-css_error named_ancestor_node(void *pw, void *n,
-               const css_qname *qname,
-               void **ancestor)
+static css_error node_is_lang(void *pw, void *n,
+               lwc_string *lang,
+               bool *match)
 {
        node *node = n;
-       UNUSED(pw);
 
-       for (node = node->parent; node != NULL; node = node->parent) {
-               bool match = false;
-               assert(lwc_string_caseless_isequal(
-                               qname->name, node->name,
-                               &match) == lwc_error_ok);
-               if (match == true)
-                       break;
-       }
+       UNUSED(pw);
+       UNUSED(node);
+       UNUSED(lang);
 
-       *ancestor = (void *) node;
+       *match = false;
 
        return CSS_OK;
 }
 
-css_error named_parent_node(void *pw, void *n,
-               const css_qname *qname,
-               void **parent)
+static css_error node_presentational_hint(void *pw, void *node,
+               uint32_t *nhints, css_hint **hints)
 {
-       node *node = n;
        UNUSED(pw);
+       UNUSED(node);
 
-       *parent = NULL;
-       if (node->parent != NULL) {
-               bool match = false;
-               assert(lwc_string_caseless_isequal(
-                               qname->name, node->parent->name, &match) ==
-                               lwc_error_ok);
-               if (match == true)
-                       *parent = (void *) node->parent;
-       }
+       *nhints = 0;
+       *hints = NULL;
 
        return CSS_OK;
 }
 
-css_error named_sibling_node(void *pw, void *n,
-               const css_qname *qname,
-               void **sibling)
+static css_error ua_default_for_property(void *pw, uint32_t property, css_hint 
*hint)
 {
-       node *node = n;
        UNUSED(pw);
 
-       *sibling = NULL;
-       if (node->prev != NULL) {
-               bool match = false;
-               assert(lwc_string_caseless_isequal(
-                               qname->name, node->prev->name, &match) ==
-                               lwc_error_ok);
-               if (match == true)
-                       *sibling = (void *) node->prev;
+       if (property == CSS_PROP_COLOR) {
+               hint->data.color = 0xff000000;
+               hint->status = CSS_COLOR_COLOR;
+       } else if (property == CSS_PROP_FONT_FAMILY) {
+               hint->data.strings = NULL;
+               hint->status = CSS_FONT_FAMILY_SANS_SERIF;
+       } else if (property == CSS_PROP_QUOTES) {
+               /* Not exactly useful :) */
+               hint->data.strings = NULL;
+               hint->status = CSS_QUOTES_NONE;
+       } else if (property == CSS_PROP_VOICE_FAMILY) {
+               /** \todo Fix this when we have voice-family done */
+               hint->data.strings = NULL;
+               hint->status = 0;
+       } else {
+               return CSS_INVALID;
        }
 
        return CSS_OK;
 }
 
-css_error named_generic_sibling_node(void *pw, void *n,
-               const css_qname *qname,
-               void **sibling)
+static css_error set_libcss_node_data(void *pw, void *n,
+               void *libcss_node_data)
 {
        node *node = n;
        UNUSED(pw);
 
-       for (node = node->prev; node != NULL; node = node->prev) {
-               bool match = false;
-               assert(lwc_string_caseless_isequal(
-                               qname->name, node->name,
-                               &match) == lwc_error_ok);
-               if (match == true)
-                       break;
-       }
-
-       *sibling = (void *) node;
+       node->libcss_node_data = libcss_node_data;
 
        return CSS_OK;
 }
 
-css_error parent_node(void *pw, void *n, void **parent)
+static css_error get_libcss_node_data(void *pw, void *n,
+               void **libcss_node_data)
 {
        node *node = n;
-
        UNUSED(pw);
 
-       *parent = (void *) node->parent;
+       /* Pass any node data back to libcss */
+       *libcss_node_data = node->libcss_node_data;
 
        return CSS_OK;
 }
 
-css_error sibling_node(void *pw, void *n, void **sibling)
-{
-       node *node = n;
+static css_unit_ctx unit_ctx = {
+       .font_size_default = 16 * (1 << CSS_RADIX_POINT),
+};
+
+static css_select_handler select_handler = {
+       CSS_SELECT_HANDLER_VERSION_1,
+
+       node_name,
+       node_classes,
+       node_id,
+       named_ancestor_node,
+       named_parent_node,
+       named_sibling_node,
+       named_generic_sibling_node,
+       parent_node,
+       sibling_node,
+       node_has_name,
+       node_has_class,
+       node_has_id,
+       node_has_attribute,
+       node_has_attribute_equal,
+       node_has_attribute_dashmatch,
+       node_has_attribute_includes,
+       node_has_attribute_prefix,
+       node_has_attribute_suffix,
+       node_has_attribute_substring,
+       node_is_root,
+       node_count_siblings,
+       node_is_empty,
+       node_is_link,
+       node_is_visited,
+       node_is_hover,
+       node_is_active,
+       node_is_focus,
+       node_is_enabled,
+       node_is_disabled,
+       node_is_checked,
+       node_is_target,
+       node_is_lang,
+       node_presentational_hint,
+       ua_default_for_property,
 
+       set_libcss_node_data,
+       get_libcss_node_data,
+};
+
+static css_error resolve_url(void *pw,
+               const char *base, lwc_string *rel, lwc_string **abs)
+{
        UNUSED(pw);
+       UNUSED(base);
 
-       *sibling = (void *) node->prev;
+       /* About as useless as possible */
+       *abs = lwc_string_ref(rel);
 
        return CSS_OK;
 }
 
-css_error node_has_name(void *pw, void *n,
-               const css_qname *qname,
-               bool *match)
+static bool fail_because_lwc_leaked = false;
+
+static void
+printing_lwc_iterator(lwc_string *str, void *pw)
 {
-       node *node = n;
        UNUSED(pw);
 
-       if (lwc_string_length(qname->name) == 1 &&
-                       lwc_string_data(qname->name)[0] == '*')
-               *match = true;
-       else
-               assert(lwc_string_caseless_isequal(node->name,
-                       qname->name, match) == lwc_error_ok);
+       printf(" DICT: %*s\n", (int)(lwc_string_length(str)), 
lwc_string_data(str));
+       fail_because_lwc_leaked = true;
+}
+
+static css_error css_font_resolution_func(void *pw, lwc_string *name,
+               css_system_font *system_font)
+{
+       lwc_error err;
+
+       if (system_font == NULL) {
+               return CSS_BADPARM;
+       }
+
+       (void)(pw);
+
+       if (strncmp(lwc_string_data(name), "special-system-font",
+                       lwc_string_length(name)) != 0) {
+               return CSS_INVALID;
+       }
+
+       system_font->style = CSS_FONT_STYLE_NORMAL;
+       system_font->variant = CSS_FONT_VARIANT_NORMAL;
+       system_font->weight = CSS_FONT_WEIGHT_NORMAL;
+       system_font->size.size = INTTOFIX(22);
+       system_font->size.unit = CSS_UNIT_PT;
+       system_font->line_height.size = INTTOFIX(33);
+       system_font->line_height.unit = CSS_UNIT_EM;
+       err = lwc_intern_string("special-system-font",
+                       strlen("special-system-font"),
+                       &system_font->family);
+       if (err != lwc_error_ok) {
+               return CSS_NOMEM;
+       }
 
        return CSS_OK;
 }
 
-css_error node_has_class(void *pw, void *n,
-               lwc_string *name,
-               bool *match)
+static void css__parse_sheet(line_ctx *ctx, const char *data, size_t len)
 {
-       node *node = n;
-       uint32_t i;
-       line_ctx *ctx = pw;
+       css_stylesheet_params params;
+       const char *p;
+       const char *end = data + len;
+       css_origin origin = CSS_ORIGIN_AUTHOR;
+       css_stylesheet *sheet;
+       sheet_ctx *temp;
+       char *media = NULL;
 
-       for (i = 0; i < node->n_attrs; i++) {
-               bool amatch = false;
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, ctx->attr_class,
-                               &amatch) == lwc_error_ok);
-               if (amatch == true)
+       /* <origin> <media_list>? */
+
+       /* Find end of origin */
+       for (p = data; p < end; p++) {
+               if (isspace(*p))
                        break;
        }
 
-       /* Classes are case-sensitive in HTML */
-       if (i != node->n_attrs && name == node->attrs[i].value)
-               *match = true;
+       if (p - data == 6 && strncasecmp(data, "author", 6) == 0)
+               origin = CSS_ORIGIN_AUTHOR;
+       else if (p - data == 4 && strncasecmp(data, "user", 4) == 0)
+               origin = CSS_ORIGIN_USER;
+       else if (p - data == 2 && strncasecmp(data, "ua", 2) == 0)
+               origin = CSS_ORIGIN_UA;
        else
-               *match = false;
+               assert(0 && "Unknown stylesheet origin");
 
-       return CSS_OK;
+       /* Skip any whitespace */
+       while (p < end && isspace(*p))
+               p++;
+
+       assert(end >= p);
+       media = malloc(end - p + 1);
+       assert(media != NULL);
+       memcpy(media, p, end - p);
+       media[end - p] = '\0';
+
+       params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
+       params.level = CSS_LEVEL_21;
+       params.charset = "UTF-8";
+       params.url = "foo";
+       params.title = "foo";
+       params.allow_quirks = false;
+       params.inline_style = false;
+       params.resolve = resolve_url;
+       params.resolve_pw = NULL;
+       params.import = NULL;
+       params.import_pw = NULL;
+       params.color = NULL;
+       params.color_pw = NULL;
+       params.font = css_font_resolution_func;
+       params.font_pw = NULL;
+
+       /** \todo How are we going to handle @import? */
+       assert(css_stylesheet_create(&params, &sheet) == CSS_OK);
+
+       /* Extend array of sheets and append new sheet to it */
+       temp = realloc(ctx->sheets,
+                       (ctx->n_sheets + 1) * sizeof(sheet_ctx));
+       assert(temp != NULL);
+
+       ctx->sheets = temp;
+
+       ctx->sheets[ctx->n_sheets].sheet = sheet;
+       ctx->sheets[ctx->n_sheets].origin = origin;
+       ctx->sheets[ctx->n_sheets].media = media;
+
+       ctx->n_sheets++;
 }
 
-css_error node_has_id(void *pw, void *n,
-               lwc_string *name,
-               bool *match)
+static void css__parse_media_list(const char **data, size_t *len, css_media 
*media)
 {
-       node *node = n;
-       uint32_t i;
-       line_ctx *ctx = pw;
+       const char *p = *data;
+       const char *end = p + *len;
+       uint64_t result = 0;
 
-       for (i = 0; i < node->n_attrs; i++) {
-               bool amatch = false;
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, ctx->attr_id, &amatch) ==
-                               lwc_error_ok);
-               if (amatch == true)
-                       break;
-       }
+       /* <medium> [ ',' <medium> ]* */
+
+       while (p < end) {
+               const char *start = p;
+
+               /* consume a medium */
+               while (isspace(*p) == false && *p != ',')
+                       p++;
+
+               if (p - start == 10 &&
+                               strncasecmp(start, "projection", 10) == 0)
+                       result |= CSS_MEDIA_PROJECTION;
+               else if (p - start == 8 &&
+                               strncasecmp(start, "handheld", 8) == 0)
+                       result |= CSS_MEDIA_HANDHELD;
+               else if (p - start == 8 &&
+                               strncasecmp(start, "embossed", 8) == 0)
+                       result |= CSS_MEDIA_EMBOSSED;
+               else if (p - start == 7 &&
+                               strncasecmp(start, "braille", 7) == 0)
+                       result |= CSS_MEDIA_BRAILLE;
+               else if (p - start == 6 &&
+                               strncasecmp(start, "speech", 6) == 0)
+                       result |= CSS_MEDIA_SPEECH;
+               else if (p - start == 6 &&
+                               strncasecmp(start, "screen", 6) == 0)
+                       result |= CSS_MEDIA_SCREEN;
+               else if (p - start == 5 &&
+                               strncasecmp(start, "print", 5) == 0)
+                       result |= CSS_MEDIA_PRINT;
+               else if (p - start == 5 &&
+                               strncasecmp(start, "aural", 5) == 0)
+                       result |= CSS_MEDIA_AURAL;
+               else if (p - start == 3 &&
+                               strncasecmp(start, "tty", 3) == 0)
+                       result |= CSS_MEDIA_TTY;
+               else if (p - start == 3 &&
+                               strncasecmp(start, "all", 3) == 0)
+                       result |= CSS_MEDIA_ALL;
+               else if (p - start == 2 &&
+                               strncasecmp(start, "tv", 2) == 0)
+                       result |= CSS_MEDIA_TV;
+               else
+                       assert(0 && "Unknown media type");
 
-       /* IDs are case-sensitive in HTML */
-       if (i != node->n_attrs && name == node->attrs[i].value)
-               *match = true;
-       else
-               *match = false;
+               /* Consume whitespace */
+               while (p < end && isspace(*p))
+                       p++;
 
-       return CSS_OK;
-}
+               /* Stop if we've reached the end */
+               if (p == end || *p != ',')
+                       break;
 
-css_error node_has_attribute(void *pw, void *n,
-               const css_qname *qname,
-               bool *match)
-{
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
+               /* Consume comma */
+               p++;
 
-       *match = false;
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
+               /* Consume whitespace */
+               while (p < end && isspace(*p))
+                       p++;
        }
 
-       return CSS_OK;
+       media->type = result;
+
+       *data = p;
+       *len = end - p;
 }
 
-css_error node_has_attribute_equal(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
+static void css__parse_pseudo_list(const char **data, size_t *len, uint32_t 
*element)
 {
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
+       const char *p = *data;
+       const char *end = p + *len;
 
-       *match = false;
+       /* <pseudo> [ ',' <pseudo> ]* */
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
-       }
+       *element = CSS_PSEUDO_ELEMENT_NONE;
 
-       if (*match == true) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, value, match) ==
-                               lwc_error_ok);
-       }
+       while (p < end) {
+               const char *start = p;
 
-       return CSS_OK;
-}
+               /* consume a pseudo */
+               while (isspace(*p) == false && *p != ',')
+                       p++;
 
-css_error node_has_attribute_includes(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
-{
-       node *node = n;
-       uint32_t i;
-       size_t vlen = lwc_string_length(value);
-       UNUSED(pw);
+               /* Pseudo elements */
+               if (p - start == 12 &&
+                               strncasecmp(start, "first-letter", 12) == 0)
+                       *element = CSS_PSEUDO_ELEMENT_FIRST_LETTER;
+               else if (p - start == 10 &&
+                               strncasecmp(start, "first-line", 10) == 0)
+                       *element = CSS_PSEUDO_ELEMENT_FIRST_LINE;
+               else if (p - start == 6 &&
+                               strncasecmp(start, "before", 6) == 0)
+                       *element = CSS_PSEUDO_ELEMENT_BEFORE;
+               else if (p - start == 5 &&
+                               strncasecmp(start, "after", 5) == 0)
+                       *element = CSS_PSEUDO_ELEMENT_AFTER;
+               else
+                       assert(0 && "Unknown pseudo");
 
-       *match = false;
+               /* Consume whitespace */
+               while (p < end && isspace(*p))
+                       p++;
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
+               /* Stop if we've reached the end */
+               if (p == end || *p != ',')
                        break;
-       }
-
-       if (*match == true) {
-               const char *p;
-               const char *start = lwc_string_data(node->attrs[i].value);
-               const char *end = start +
-                               lwc_string_length(node->attrs[i].value);
 
-               *match = false;
-
-               for (p = start; p < end; p++) {
-                       if (*p == ' ') {
-                               if ((size_t) (p - start) == vlen &&
-                                               strncasecmp(start,
-                                                       lwc_string_data(value),
-                                                       vlen) == 0) {
-                                       *match = true;
-                                       break;
-                               }
+               /* Consume comma */
+               p++;
 
-                               start = p + 1;
-                       }
-               }
+               /* Consume whitespace */
+               while (p < end && isspace(*p))
+                       p++;
        }
 
-       return CSS_OK;
+       *data = p;
+       *len = end - p;
 }
 
-css_error node_has_attribute_dashmatch(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
+static void css__parse_tree(line_ctx *ctx, const char *data, size_t len)
 {
-       node *node = n;
-       uint32_t i;
-       size_t vlen = lwc_string_length(value);
-       UNUSED(pw);
+       const char *p = data;
+       const char *end = data + len;
+       size_t left;
 
-       *match = false;
+       /* [ <media_list> <pseudo>? ] ? */
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
-       }
+       ctx->media.type = CSS_MEDIA_ALL;
+       ctx->pseudo_element = CSS_PSEUDO_ELEMENT_NONE;
 
-       if (*match == true) {
-               const char *p;
-               const char *start = lwc_string_data(node->attrs[i].value);
-               const char *end = start +
-                               lwc_string_length(node->attrs[i].value);
+       /* Consume any leading whitespace */
+       while (p < end && isspace(*p))
+               p++;
 
-               *match = false;
+       if (p < end) {
+               left = end - p;
 
-               for (p = start; p < end; p++) {
-                       if (*p == '-') {
-                               if ((size_t) (p - start) == vlen &&
-                                               strncasecmp(start,
-                                                       lwc_string_data(value),
-                                                       vlen) == 0) {
-                                       *match = true;
-                                       break;
-                               }
+               css__parse_media_list(&p, &left, &ctx->media);
 
-                               start = p + 1;
-                       }
-               }
+               end = p + left;
        }
 
-       return CSS_OK;
+       if (p < end) {
+               left = end - p;
+
+               css__parse_pseudo_list(&p, &left, &ctx->pseudo_element);
+       }
 }
 
-css_error node_has_attribute_prefix(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
+static void css__parse_expected(line_ctx *ctx, const char *data, size_t len)
 {
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
-
-       *match = false;
+       while (ctx->expused + len >= ctx->explen) {
+               size_t required = ctx->explen == 0 ? 64 : ctx->explen * 2;
+               char *temp = realloc(ctx->exp, required);
+               if (temp == NULL) {
+                       assert(0 && "No memory for expected output");
+               }
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
+               ctx->exp = temp;
+               ctx->explen = required;
        }
 
-       if (*match == true) {
-               size_t len = lwc_string_length(node->attrs[i].value);
-               const char *data = lwc_string_data(node->attrs[i].value);
-
-               size_t vlen = lwc_string_length(value);
-               const char *vdata = lwc_string_data(value);
-
-               if (len < vlen)
-                       *match = false;
-               else
-                       *match = (strncasecmp(data, vdata, vlen) == 0);
-       }
+       memcpy(ctx->exp + ctx->expused, data, len);
 
-       return CSS_OK;
+       ctx->expused += len;
 }
 
-css_error node_has_attribute_suffix(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
+static void css__parse_tree_data(line_ctx *ctx, const char *data, size_t len)
 {
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
+       const char *p = data;
+       const char *end = data + len;
+       const char *name = NULL;
+       const char *value = NULL;
+       size_t namelen = 0;
+       size_t valuelen = 0;
+       uint32_t depth = 0;
+       bool target = false;
 
-       *match = false;
+       /* ' '{depth+1} [ <element> '*'? | <attr> ]
+        *
+        * <element> ::= [^=*[:space:]]+
+        * <attr>    ::= [^=*[:space:]]+ '=' [^[:space:]]*
+        */
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
+       while (p < end && isspace(*p)) {
+               depth++;
+               p++;
+       }
+       depth--;
+
+       /* Get element/attribute name */
+       name = p;
+       while (p < end && *p != '=' && *p != '*' && isspace(*p) == false) {
+               namelen++;
+               p++;
        }
 
-       if (*match == true) {
-               size_t len = lwc_string_length(node->attrs[i].value);
-               const char *data = lwc_string_data(node->attrs[i].value);
+       /* Skip whitespace */
+       while (p < end && isspace(*p))
+               p++;
 
-               size_t vlen = lwc_string_length(value);
-               const char *vdata = lwc_string_data(value);
+       if (p < end && *p == '=') {
+               /* Attribute value */
+               p++;
 
-               size_t suffix_start = len - vlen;
+               value = p;
 
-               if (len < vlen)
-                       *match = false;
-               else {
-                       *match = (strncasecmp(data + suffix_start,
-                                       vdata, vlen) == 0);
+               while (p < end && isspace(*p) == false) {
+                       valuelen++;
+                       p++;
                }
+       } else if (p < end && *p == '*') {
+               /* Element is target node */
+               target = true;
        }
 
-       return CSS_OK;
-}
-
-css_error node_has_attribute_substring(void *pw, void *n,
-               const css_qname *qname,
-               lwc_string *value,
-               bool *match)
-{
-       node *node = n;
-       uint32_t i;
-       UNUSED(pw);
-
-       *match = false;
+       if (value == NULL) {
+               /* We have an element, so create it */
+               node *n = malloc(sizeof(node));
+               assert(n != NULL);
 
-       for (i = 0; i < node->n_attrs; i++) {
-               assert(lwc_string_caseless_isequal(
-                               node->attrs[i].name, qname->name, match) ==
-                               lwc_error_ok);
-               if (*match == true)
-                       break;
-       }
+               memset(n, 0, sizeof(node));
 
-       if (*match == true) {
-               size_t len = lwc_string_length(node->attrs[i].value);
-               const char *data = lwc_string_data(node->attrs[i].value);
+               lwc_intern_string(name, namelen, &n->name);
 
-               size_t vlen = lwc_string_length(value);
-               const char *vdata = lwc_string_data(value);
+               /* Insert it into tree */
+               if (ctx->tree == NULL) {
+                       ctx->tree = n;
+               } else {
+                       assert(depth > 0);
+                       assert(depth <= ctx->depth + 1);
 
-               const char *last_start = data + len - vlen;
+                       /* Find node to insert into */
+                       while (depth <= ctx->depth) {
+                               ctx->depth--;
+                               ctx->current = ctx->current->parent;
+                       }
 
-               if (len < vlen)
-                       *match = false;
-               else {
-                       while (data <= last_start) {
-                               if (strncasecmp(data, vdata, vlen) == 0) {
-                                       *match = true;
-                                       break;
-                               }
+                       /* Insert into current node */
+                       if (ctx->current->children == NULL) {
+                               ctx->current->children = n;
+                               ctx->current->last_child = n;
+                       } else {
+                               ctx->current->last_child->next = n;
+                               n->prev = ctx->current->last_child;
 
-                               data++;
+                               ctx->current->last_child = n;
                        }
-
-                       if (data > last_start)
-                               *match = false;
+                       n->parent = ctx->current;
                }
-       }
 
-       return CSS_OK;
-}
+               ctx->current = n;
+               ctx->depth = depth;
 
-css_error node_is_root(void *pw, void *n, bool *match)
-{
-       node *node = n;
-       UNUSED(pw);
+               /* Mark the target, if it's us */
+               if (target)
+                       ctx->target = n;
+       } else {
+               /* New attribute */
+               bool amatch = false;
+               attribute *attr;
+               node *n = ctx->current;
 
-       *match = (node->parent == NULL);
+               attribute *temp = realloc(n->attrs,
+                               (n->n_attrs + 1) * sizeof(attribute));
+               assert(temp != NULL);
 
-       return CSS_OK;
-}
+               n->attrs = temp;
 
-css_error node_count_siblings(void *pw, void *n,
-               bool same_name, bool after, int32_t *count)
-{
-       int32_t cnt = 0;
-       bool match = false;
-       node *node = n;
-       lwc_string *name = node->name;
-       UNUSED(pw);
+               attr = &n->attrs[n->n_attrs];
 
-       if (after) {
-               while (node->next != NULL) {
-                       if (same_name) {
-                               assert(lwc_string_caseless_isequal(
-                                       name, node->next->name, &match) ==
-                                       lwc_error_ok);
+               lwc_intern_string(name, namelen, &attr->name);
+               lwc_intern_string(value, valuelen, &attr->value);
 
-                               if (match)
-                                       cnt++;
-                       } else {
-                               cnt++;
-                       }
+               assert(lwc_string_caseless_isequal(
+                               n->attrs[n->n_attrs].name,
+                               ctx->attr_class, &amatch) == lwc_error_ok);
+               if (amatch == true) {
+                       n->classes = realloc(NULL, sizeof(lwc_string **));
+                       assert(n->classes != NULL);
 
-                       node = node->next;
+                       n->classes[0] = lwc_string_ref(
+                                       n->attrs[n->n_attrs].
+                                       value);
+                       n->n_classes = 1;
                }
-       } else {
-               while (node->prev != NULL) {
-                       if (same_name) {
-                               assert(lwc_string_caseless_isequal(
-                                       name, node->prev->name, &match) ==
-                                       lwc_error_ok);
-
-                               if (match)
-                                       cnt++;
-                       } else {
-                               cnt++;
-                       }
 
-                       node = node->prev;
-               }
+               n->n_attrs++;
        }
-
-       *count = cnt;
-
-       return CSS_OK;
 }
 
-css_error node_is_empty(void *pw, void *n, bool *match)
-{
-       node *node = n;
-       UNUSED(pw);
-
-       *match = (node->children == NULL);
-
-       return CSS_OK;
-}
 
-css_error node_is_link(void *pw, void *n, bool *match)
+static void run_test_select_tree(css_select_ctx *select,
+               node *node, line_ctx *ctx,
+               char *buf, size_t *buflen)
 {
-       node *node = n;
+       css_select_results *sr;
+       struct node *n = NULL;
 
-       UNUSED(pw);
-       UNUSED(node);
+       if (node->parent == NULL) {
+               unit_ctx.root_style = NULL;
+       }
 
-       *match = false;
 
-       return CSS_OK;
-}
+       assert(css_select_style(select, node, &unit_ctx, &ctx->media, NULL,
+                       &select_handler, ctx, &sr) == CSS_OK);
 
-css_error node_is_visited(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       if (node->parent != NULL) {
+               css_computed_style *composed;
+               assert(css_computed_style_compose(
+                               node->parent->sr->styles[ctx->pseudo_element],
+                               sr->styles[ctx->pseudo_element],
+                               &unit_ctx,
+                               &composed) == CSS_OK);
+               css_computed_style_destroy(sr->styles[ctx->pseudo_element]);
+               sr->styles[ctx->pseudo_element] = composed;
+       }
 
-       UNUSED(pw);
-       UNUSED(node);
+       node->sr = sr;
 
-       *match = false;
+       if (node == ctx->target) {
+               dump_computed_style(sr->styles[ctx->pseudo_element],
+                               buf, buflen);
+       }
 
-       return CSS_OK;
+       if (node->parent == NULL) {
+               unit_ctx.root_style = node->sr->styles[ctx->pseudo_element];
+       }
+
+       for (n = node->children; n != NULL; n = n->next) {
+               run_test_select_tree(select, n, ctx, buf, buflen);
+       }
 }
 
-css_error node_is_hover(void *pw, void *n, bool *match)
+static void show_differences(size_t len, const char *exp, const char *res)
 {
-       node *node = n;
-
-       UNUSED(pw);
-       UNUSED(node);
+       const char *pos_exp, *opos_exp;
+       const char *pos_res, *opos_res;
 
-       *match = false;
+       opos_exp = pos_exp = exp;
+       opos_res = pos_res = res;
 
-       return CSS_OK;
+       printf("Line differences:\n");
+       while (pos_exp < exp + len && pos_res < res + len) {
+               if (*pos_exp == '\n' && *pos_res == '\n') {
+                       if (pos_exp - opos_exp != pos_res - opos_res ||
+                                       memcmp(opos_exp, opos_res,
+                                       pos_exp - opos_exp) != 0) {
+                               printf("Expected:\t%.*s\n",
+                                               (int)(pos_exp - opos_exp),
+                                               opos_exp);
+                               printf("  Result:\t%.*s\n",
+                                               (int)(pos_res - opos_res),
+                                               opos_res);
+                               printf("\n");
+                       }
+                       opos_exp = ++pos_exp;
+                       opos_res = ++pos_res;
+               } else if (*pos_exp == '\n') {
+                       pos_res++;
+               } else if (*pos_res == '\n') {
+                       pos_exp++;
+               } else {
+                       pos_exp++;
+                       pos_res++;
+               }
+       }
 }
 
-css_error node_is_active(void *pw, void *n, bool *match)
+static void destroy_tree(node *root)
 {
-       node *node = n;
+       node *n, *p;
+       uint32_t i;
 
-       UNUSED(pw);
-       UNUSED(node);
+       for (n = root->children; n != NULL; n = p) {
+               p = n->next;
 
-       *match = false;
+               destroy_tree(n);
+       }
 
-       return CSS_OK;
-}
+       css_select_results_destroy(root->sr);
 
-css_error node_is_focus(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       for (i = 0; i < root->n_attrs; ++i) {
+               lwc_string_unref(root->attrs[i].name);
+               lwc_string_unref(root->attrs[i].value);
+       }
+       free(root->attrs);
 
-       UNUSED(pw);
-       UNUSED(node);
+       if (root->classes != NULL) {
+               for (i = 0; i < root->n_classes; ++i) {
+                       lwc_string_unref(root->classes[i]);
+               }
+               free(root->classes);
+       }
 
-       *match = false;
+       if (root->libcss_node_data != NULL) {
+               css_libcss_node_data_handler(&select_handler, CSS_NODE_DELETED,
+                               NULL, root, NULL, root->libcss_node_data);
+       }
 
-       return CSS_OK;
+       lwc_string_unref(root->name);
+       free(root);
 }
 
-css_error node_is_enabled(void *pw, void *n, bool *match)
+static void run_test(line_ctx *ctx, const char *exp, size_t explen)
 {
-       node *node = n;
-
-       UNUSED(pw);
-       UNUSED(node);
+       css_select_ctx *select;
+       css_select_results *results;
+       uint32_t i;
+       char *buf;
+       size_t buflen;
+       static int testnum;
 
-       *match = false;
+       UNUSED(exp);
 
-       return CSS_OK;
-}
+       buf = malloc(8192);
+       if (buf == NULL) {
+               assert(0 && "No memory for result data");
+       }
+       buflen = 8192;
 
-css_error node_is_disabled(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       assert(css_select_ctx_create(&select) == CSS_OK);
 
-       UNUSED(pw);
-       UNUSED(node);
+       for (i = 0; i < ctx->n_sheets; i++) {
+               assert(css_select_ctx_append_sheet(select,
+                               ctx->sheets[i].sheet, ctx->sheets[i].origin,
+                               ctx->sheets[i].media) == CSS_OK);
+       }
 
-       *match = false;
+       testnum++;
 
-       return CSS_OK;
-}
+       run_test_select_tree(select, ctx->tree, ctx, buf, &buflen);
 
-css_error node_is_checked(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       results = ctx->target->sr;
+       assert(results->styles[ctx->pseudo_element] != NULL);
 
-       UNUSED(pw);
-       UNUSED(node);
+       if (8192 - buflen != explen || memcmp(buf, exp, explen) != 0) {
+               size_t len = 8192 - buflen < explen ? 8192 - buflen : explen;
+               printf("Expected (%u):\n%.*s\n",
+                               (int) explen, (int) explen, exp);
+               printf("Result (%u):\n%.*s\n", (int) (8192 - buflen),
+                       (int) (8192 - buflen), buf);
 
-       *match = false;
+               show_differences(len, exp, buf);
+               assert(0 && "Result doesn't match expected");
+       }
 
-       return CSS_OK;
-}
+       /* Clean up */
+       css_select_ctx_destroy(select);
+       destroy_tree(ctx->tree);
 
-css_error node_is_target(void *pw, void *n, bool *match)
-{
-       node *node = n;
+       for (i = 0; i < ctx->n_sheets; i++) {
+               css_stylesheet_destroy(ctx->sheets[i].sheet);
+               free(ctx->sheets[i].media);
+       }
 
-       UNUSED(pw);
-       UNUSED(node);
+       ctx->tree = NULL;
+       ctx->current = NULL;
+       ctx->depth = 0;
+       ctx->n_sheets = 0;
+       free(ctx->sheets);
+       ctx->sheets = NULL;
+       ctx->target = NULL;
 
-       *match = false;
+       free(buf);
 
-       return CSS_OK;
+       printf("Test %d: PASS\n", testnum);
 }
 
-css_error node_is_lang(void *pw, void *n,
-               lwc_string *lang,
-               bool *match)
+static bool handle_line(const char *data, size_t datalen, void *pw)
 {
-       node *node = n;
-
-       UNUSED(pw);
-       UNUSED(node);
-       UNUSED(lang);
+       line_ctx *ctx = (line_ctx *) pw;
+       css_error error;
 
-       *match = false;
+       if (data[0] == '#') {
+               if (ctx->intree) {
+                       if (strncasecmp(data+1, "errors", 6) == 0) {
+                               ctx->intree = false;
+                               ctx->insheet = false;
+                               ctx->inerrors = true;
+                               ctx->inexp = false;
+                       } else {
+                               /* Assume start of stylesheet */
+                               css__parse_sheet(ctx, data + 1, datalen - 1);
 
-       return CSS_OK;
-}
+                               ctx->intree = false;
+                               ctx->insheet = true;
+                               ctx->inerrors = false;
+                               ctx->inexp = false;
+                       }
+               } else if (ctx->insheet) {
+                       if (strncasecmp(data+1, "errors", 6) == 0) {
+                               assert(css_stylesheet_data_done(
+                                       ctx->sheets[ctx->n_sheets - 1].sheet)
+                                       == CSS_OK);
 
-css_error node_presentational_hint(void *pw, void *node,
-               uint32_t *nhints, css_hint **hints)
-{
-       UNUSED(pw);
-       UNUSED(node);
+                               ctx->intree = false;
+                               ctx->insheet = false;
+                               ctx->inerrors = true;
+                               ctx->inexp = false;
+                       } else if (strncasecmp(data+1, "ua", 2) == 0 ||
+                                       strncasecmp(data+1, "user", 4) == 0 ||
+                                       strncasecmp(data+1, "author", 6) == 0) {
+                               assert(css_stylesheet_data_done(
+                                       ctx->sheets[ctx->n_sheets - 1].sheet)
+                                       == CSS_OK);
 
-       *nhints = 0;
-       *hints = NULL;
+                               css__parse_sheet(ctx, data + 1, datalen - 1);
+                       } else {
+                               error = css_stylesheet_append_data(
+                                       ctx->sheets[ctx->n_sheets - 1].sheet,
+                                       (const uint8_t *) data,
+                                       datalen);
+                               assert(error == CSS_OK ||
+                                               error == CSS_NEEDDATA);
+                       }
+               } else if (ctx->inerrors) {
+                       ctx->intree = false;
+                       ctx->insheet = false;
+                       ctx->inerrors = false;
+                       ctx->inexp = true;
+               } else if (ctx->inexp) {
+                       /* This marks end of testcase, so run it */
+                       run_test(ctx, ctx->exp, ctx->expused);
 
-       return CSS_OK;
-}
+                       ctx->expused = 0;
 
-css_error ua_default_for_property(void *pw, uint32_t property, css_hint *hint)
-{
-       UNUSED(pw);
+                       ctx->intree = false;
+                       ctx->insheet = false;
+                       ctx->inerrors = false;
+                       ctx->inexp = false;
+               } else {
+                       /* Start state */
+                       if (strncasecmp(data+1, "tree", 4) == 0) {
+                               css__parse_tree(ctx, data + 5, datalen - 5);
 
-       if (property == CSS_PROP_COLOR) {
-               hint->data.color = 0xff000000;
-               hint->status = CSS_COLOR_COLOR;
-       } else if (property == CSS_PROP_FONT_FAMILY) {
-               hint->data.strings = NULL;
-               hint->status = CSS_FONT_FAMILY_SANS_SERIF;
-       } else if (property == CSS_PROP_QUOTES) {
-               /* Not exactly useful :) */
-               hint->data.strings = NULL;
-               hint->status = CSS_QUOTES_NONE;
-       } else if (property == CSS_PROP_VOICE_FAMILY) {
-               /** \todo Fix this when we have voice-family done */
-               hint->data.strings = NULL;
-               hint->status = 0;
+                               ctx->intree = true;
+                               ctx->insheet = false;
+                               ctx->inerrors = false;
+                               ctx->inexp = false;
+                       }
+               }
        } else {
-               return CSS_INVALID;
+               if (ctx->intree) {
+                       /* Not interested in the '|' */
+                       css__parse_tree_data(ctx, data + 1, datalen - 1);
+               } else if (ctx->insheet) {
+                       error = css_stylesheet_append_data(
+                                       ctx->sheets[ctx->n_sheets - 1].sheet,
+                                       (const uint8_t *) data, datalen);
+                       assert(error == CSS_OK || error == CSS_NEEDDATA);
+               } else if (ctx->inexp) {
+                       css__parse_expected(ctx, data, datalen);
+               }
        }
 
-       return CSS_OK;
+       return true;
 }
 
-static css_error set_libcss_node_data(void *pw, void *n,
-               void *libcss_node_data)
+int main(int argc, char **argv)
 {
-       node *node = n;
-       UNUSED(pw);
+       line_ctx ctx;
 
-       node->libcss_node_data = libcss_node_data;
+       if (argc != 2) {
+               printf("Usage: %s <filename>\n", argv[0]);
+               return 1;
+       }
 
-       return CSS_OK;
-}
+       memset(&ctx, 0, sizeof(ctx));
 
-static css_error get_libcss_node_data(void *pw, void *n,
-               void **libcss_node_data)
-{
-       node *node = n;
-       UNUSED(pw);
 
-       /* Pass any node data back to libcss */
-       *libcss_node_data = node->libcss_node_data;
+       lwc_intern_string("class", SLEN("class"), &ctx.attr_class);
+       lwc_intern_string("id", SLEN("id"), &ctx.attr_id);
 
-       return CSS_OK;
-}
+       assert(css__parse_testfile(argv[1], handle_line, &ctx) == true);
+
+       /* and run final test */
+       if (ctx.tree != NULL)
+               run_test(&ctx, ctx.exp, ctx.expused);
+
+       free(ctx.exp);
+
+       lwc_string_unref(ctx.attr_class);
+       lwc_string_unref(ctx.attr_id);
+
+       lwc_iterate_strings(printing_lwc_iterator, NULL);
 
+       assert(fail_because_lwc_leaked == false);
 
+       printf("PASS\n");
+       return 0;
+}


-- 
Cascading Style Sheets library
_______________________________________________
netsurf-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to