X-Git-Url: https://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=src%2Fsvgtiny_css.c;h=ce31ef2763a83d43ff2e6ece8fd938e9a6125456;hb=c94da068c1d10b9de282396355565ec7214d4fa3;hp=c7faffddef88e447fc1618805cbb450050ff955a;hpb=99489346fc8214d7c9473b89361b0c8d17b985db;p=libsvgtiny.git diff --git a/src/svgtiny_css.c b/src/svgtiny_css.c index c7faffd..ce31ef2 100644 --- a/src/svgtiny_css.c +++ b/src/svgtiny_css.c @@ -1,4 +1,5 @@ #include +#include /* strncasecmp */ #include "svgtiny.h" #include "svgtiny_internal.h" @@ -15,6 +16,51 @@ 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 *word, + bool *match); +static css_error node_has_attribute_prefix(void *pw, void *node, + const css_qname *qname, lwc_string *prefix, + bool *match); +static css_error node_has_attribute_suffix(void *pw, void *node, + const css_qname *qname, lwc_string *suffix, + bool *match); +static css_error node_has_attribute_substring(void *pw, void *node, + const css_qname *qname, lwc_string *substring, + 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 *is_empty); +static css_error node_is_link(void *pw, void *node, bool *is_link); +static css_error node_is_visited(void *pw, void *node, bool *is_visited); +static css_error node_is_hover(void *pw, void *node, bool *is_hover); +static css_error node_is_active(void *pw, void *node, bool *is_active); +static css_error node_is_focus(void *pw, void *node, bool *is_focus); +static css_error node_is_enabled(void *pw, void *node, bool *is_enabled); +static css_error node_is_disabled(void *pw, void *node, bool *is_disabled); +static css_error node_is_checked(void *pw, void *node, bool *is_checked); +static css_error node_is_target(void *pw, void *node, bool *is_target); +static css_error node_is_lang(void *pw, void *node, + lwc_string *lang, bool *is_lang); +static css_error ua_default_for_property(void *pw, uint32_t property, + css_hint *hint); +static css_error set_libcss_node_data(void *pw, void *node, + void *libcss_node_data); /** @@ -504,3 +550,1278 @@ css_error sibling_node(void *pw, void *node, void **sibling) return CSS_OK; } + + +/** + * Test the given node for the given name + * + * This will return true (via the "match" pointer) if the libdom node + * has the given name or if that name is the universal selector; + * otherwise it returns false. The comparison is case-sensitive. It + * corresponds to a rule like "body { ... }" in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param qname Name to check for + * \param match Pointer to the test result + * + * \return Always returns CSS_OK + */ +css_error node_has_name(void *pw, void *node, + const css_qname *qname, bool *match) +{ + struct svgtiny_parse_state *state; + dom_string *name; + dom_exception err; + + /* Start by checking to see if qname is the universal selector */ + state = (struct svgtiny_parse_state *)pw; + if (lwc_string_isequal(qname->name, + state->interned_universal, match) == lwc_error_ok) { + if (*match) { + /* It's the universal selector. In NetSurf, all node + * names match the universal selector, and nothing in + * the libcss documentation suggests another approach, + * so we follow NetSurf here. */ + return CSS_OK; + } + } + + err = dom_node_get_node_name((dom_node *)node, &name); + if (err != DOM_NO_ERR) { + return CSS_OK; + } + + /* Unlike with HTML, SVG element names are case-sensitive */ + *match = dom_string_lwc_isequal(name, qname->name); + dom_string_unref(name); + + return CSS_OK; +} + + +/** + * Test the given node for the given class + * + * This will return true (via the "match" pointer) if the libdom node + * has the given class. The comparison is case-sensitive. It + * corresponds to node.class in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param name Class name to check for + * \param match Pointer to the test result + * + * \return Always returns CSS_OK + */ +css_error node_has_class(void *pw, void *node, + lwc_string *name, bool *match) +{ + UNUSED(pw); + /* libdom implements this for us and apparently it cannot fail */ + dom_element_has_class((dom_node *)node, name, match); + return CSS_OK; +} + + +/** + * Test the given node for the given id + * + * This will return true (via the "match" pointer) if the libdom node + * has the given id. The comparison is case-sensitive. It corresponds + * to node#id in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param name Id to check for + * \param match Pointer to the test result + * + * \return Always returns CSS_OK + */ +css_error node_has_id(void *pw, void *node, + lwc_string *name, bool *match) +{ + dom_string *attr; + dom_exception err; + struct svgtiny_parse_state *state; + + attr = NULL; /* a priori the "id" attribute may not exist */ + *match = false; /* default to no match */ + + state = (struct svgtiny_parse_state *)pw; + err = dom_element_get_attribute((dom_node *)node, + state->interned_id, &attr); + if (err != DOM_NO_ERR || attr == NULL) { + return CSS_OK; + } + + *match = dom_string_lwc_isequal(attr, name); + dom_string_unref(attr); + + return CSS_OK; +} + + +/** + * Test the given node for the given attribute + * + * This will return true (via the "match" pointer) if the libdom node + * has an attribute with the given name. The comparison is + * case-sensitive. It corresponds to node[attr] in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param qname Attribute name to check for + * \param match Pointer to the test result + * + * \return Returns CSS_OK if successful and CSS_NOMEM if anything + * goes wrong + */ +css_error node_has_attribute(void *pw, void *node, + const css_qname *qname, bool *match) +{ + UNUSED(pw); + dom_string *name; + dom_exception err; + + /* intern the attribute name as a dom_string so we can + * delegate to dom_element_has_attribute() */ + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), + &name); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + err = dom_element_has_attribute((dom_node *)node, name, match); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + return CSS_OK; + } + + dom_string_unref(name); + return CSS_OK; +} + + +/** + * Test the given node for an attribute with a specific value + * + * This will return true (via the "match" pointer) if the libdom node + * has an attribute with the given name and value. The comparison is + * case-sensitive. It corresponds to node[attr=value] in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param qname Attribute name to check for + * \param value Attribute value to check for + * \param match Pointer to the test result + * + * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot + * intern the attribute name (which usually indicates memory + * exhaustion) + */ +css_error node_has_attribute_equal(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + /* Implementation note: NetSurf always returns "no match" when + * the value is empty (length zero). We allow it, because why + * not? */ + + UNUSED(pw); + dom_string *name; + dom_string *attr_val; + dom_exception err; + + /* Intern the attribute name as a dom_string so we can + * use dom_element_get_attribute() */ + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), + &name); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + err = dom_element_get_attribute((dom_node *)node, name, &attr_val); + if ((err != DOM_NO_ERR) || (attr_val == NULL)) { + /* There was an error getting the attribute's value or + * the attribute doesn't exist. So, no match? */ + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + /* Otherwise, we have the attribute value from the given node + * and all we need to do is compare. */ + dom_string_unref(name); + *match = dom_string_lwc_isequal(attr_val, value); + dom_string_unref(attr_val); + + return CSS_OK; +} + + +/** + * Test the given node for an attribute with a specific value, + * possibly followed by a single hyphen + * + * This will return true (via the "match" pointer) if the libdom node + * has an attribute with the given name and value or with the given + * name and a value that is followed by exactly one hyphen. The + * comparison is case-sensitive. This corresponds to [attr|=value] + * in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param qname Attribute name to check for + * \param value Attribute value to check for + * \param match Pointer to the test result + * + * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot + * intern the attribute name (which usually indicates memory + * exhaustion) + */ +css_error node_has_attribute_dashmatch(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + /* Implementation note: NetSurf always returns "no match" when + * the value is empty (length zero). We allow it, because why + * not? */ + + UNUSED(pw); + dom_string *name; + dom_string *attr_val; + dom_exception err; + + const char *vdata; /* to hold the data underlying "value" */ + size_t vdata_len; + const char *avdata; /* to hold the found attribute value data */ + size_t avdata_len; + + /* Intern the attribute name as a dom_string so we can + * use dom_element_get_attribute() */ + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), + &name); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + err = dom_element_get_attribute((dom_node *)node, name, &attr_val); + if ((err != DOM_NO_ERR) || (attr_val == NULL)) { + /* There was an error getting the attribute's value or + * the attribute doesn't exist. So, no match? */ + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + /* Otherwise, we have the attribute value from the given node + * and all we need to do is compare. */ + dom_string_unref(name); + *match = dom_string_lwc_isequal(attr_val, value); + if (*match) { + /* Exact match, we're done */ + dom_string_unref(attr_val); + return CSS_OK; + } + + /* No exact match, try it with a hyphen on the end */ + vdata = lwc_string_data(value); /* needle */ + vdata_len = lwc_string_length(value); + avdata = dom_string_data(attr_val); /* haystack */ + avdata_len = dom_string_byte_length(attr_val); + dom_string_unref(attr_val); + + if (avdata_len > vdata_len && avdata[vdata_len] == '-') { + if (strncasecmp(avdata, vdata, vdata_len) == 0) { + /* If there's a hyphen in the right position, + * it suffices to compare the strings only up + * to the hyphen */ + *match = true; + } + } + + return CSS_OK; +} + + +/** + * Test the given node for an attribute whose value is a + * space-separated list of words, one of which is the given word + * + * This will return true (via the "match" pointer) if the libdom node + * has an attribute with the given name and whose value when + * considered as a space-separated list of words contains the given + * word. The comparison is case-sensitive. This corresponds to + * [attr~=value] in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param qname Attribute name to check for + * \param word Value word to check for + * \param match Pointer to the test result + * + * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot + * intern the attribute name (which usually indicates memory + * exhaustion) + */ +css_error node_has_attribute_includes(void *pw, void *node, + const css_qname *qname, lwc_string *word, + bool *match) +{ + UNUSED(pw); + + dom_string *name; + dom_string *attr_val; + dom_exception err; + size_t wordlen; /* length of "word" */ + + /* pointers used to parse a space-separated list of words */ + const char *p; + const char *start; + const char *end; + + *match = false; /* default to no match */ + + wordlen = lwc_string_length(word); + if (wordlen == 0) { + /* In this case, the spec says that "if 'val' is the + * empty string, it will never represent anything." */ + return CSS_OK; + } + + /* Intern the attribute name as a dom_string so we can + * use dom_element_get_attribute() */ + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), + &name); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + err = dom_element_get_attribute((dom_node *)node, name, &attr_val); + if ((err != DOM_NO_ERR) || (attr_val == NULL)) { + /* There was an error getting the attribute's value or + * the attribute doesn't exist. So, no match? */ + dom_string_unref(name); + return CSS_OK; + } + + /* Parse the list comparing each word against "word" */ + start = dom_string_data(attr_val); + end = start + dom_string_byte_length(attr_val); + dom_string_unref(attr_val); + + for (p = start; p <= end; p++) { + /* Move forward until we find the end of the first word */ + if (*p == ' ' || *p == '\0') { + /* If the length of that word is the length of the + * word we're looking for, do the comparison. */ + if ((size_t) (p - start) == wordlen && + strncasecmp(start, + lwc_string_data(word), + wordlen) == 0) { + *match = true; + break; + } + /* No match? Set "start" to the beginning of + * the next word and loop. */ + start = p + 1; + } + } + + return CSS_OK; +} + + +/** + * Test the given node for an attribute whose value begins with the + * given prefix + * + * This will return true (via the "match" pointer) if the libdom node + * has an attribute with the given name and whose value begins with + * the given prefix string. The comparison is case-sensitive. This + * corresponds to [attr^=value] in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param qname Attribute name to check for + * \param prefix Value prefix to check for + * \param match Pointer to the test result + * + * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot + * intern the attribute name (which usually indicates memory + * exhaustion) + */ +css_error node_has_attribute_prefix(void *pw, void *node, + const css_qname *qname, lwc_string *prefix, + bool *match) +{ + UNUSED(pw); + dom_string *name; + dom_string *attr_val; + dom_exception err; + const char *avdata; /* attribute value data */ + size_t avdata_len; /* length of that attribute value data */ + size_t prefixlen; /* length of "prefix" */ + + prefixlen = lwc_string_length(prefix); + if (prefixlen == 0) { + /* In this case, the spec says that "if 'val' is the + * empty string, it will never represent anything." */ + return CSS_OK; + } + + /* Intern the attribute name as a dom_string so we can + * use dom_element_get_attribute() */ + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), + &name); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + err = dom_element_get_attribute((dom_node *)node, name, &attr_val); + if ((err != DOM_NO_ERR) || (attr_val == NULL)) { + /* There was an error getting the attribute's value or + * the attribute doesn't exist. So, no match? */ + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + /* Otherwise, we have the attribute value from the given node, + * and the first thing we want to do is check to see if the + * whole thing matches the prefix. */ + dom_string_unref(name); + *match = dom_string_lwc_isequal(attr_val, prefix); + + /* If not, check to see if an, uh, prefix matches the + * prefix */ + if (*match == false) { + avdata = dom_string_data(attr_val); + avdata_len = dom_string_byte_length(attr_val); + if ((avdata_len >= prefixlen) && + (strncasecmp(avdata, + lwc_string_data(prefix), + prefixlen) == 0)) { + /* Use strncasecmp to compare only the first + * "n" characters, where "n" is the length of + * the prefix. */ + *match = true; + } + } + + dom_string_unref(attr_val); + + return CSS_OK; +} + + +/** + * Test the given node for an attribute whose value end with the + * given suffix + * + * This will return true (via the "match" pointer) if the libdom node + * has an attribute with the given name and whose value ends with + * the given suffix string. The comparison is case-sensitive. This + * corresponds to [attr$=value] in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param qname Attribute name to check for + * \param suffix Value suffix to check for + * \param match Pointer to the test result + * + * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot + * intern the attribute name (which usually indicates memory + * exhaustion) + */ +css_error node_has_attribute_suffix(void *pw, void *node, + const css_qname *qname, lwc_string *suffix, + bool *match) +{ + UNUSED(pw); + dom_string *name; + dom_string *attr_val; + dom_exception err; + const char *avdata; /* attribute value data */ + size_t avdata_len; /* length of that attribute value data */ + size_t suffixlen; /* length of "suffix" */ + + /* convenience pointer we'll use when matching the suffix */ + const char *suffix_start; + + suffixlen = lwc_string_length(suffix); + if (suffixlen == 0) { + /* In this case, the spec says that "if 'val' is the + * empty string, it will never represent anything." */ + return CSS_OK; + } + + /* Intern the attribute name as a dom_string so we can + * use dom_element_get_attribute() */ + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), + &name); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + err = dom_element_get_attribute((dom_node *)node, name, &attr_val); + if ((err != DOM_NO_ERR) || (attr_val == NULL)) { + /* There was an error getting the attribute's value or + * the attribute doesn't exist. So, no match? */ + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + /* Otherwise, we have the attribute value from the given node, + * and the first thing we want to do is check to see if the + * whole thing matches the suffix. */ + dom_string_unref(name); + *match = dom_string_lwc_isequal(attr_val, suffix); + + /* If not, check to see if an, uh, suffix matches the + * suffix */ + if (*match == false) { + avdata = dom_string_data(attr_val); + avdata_len = dom_string_byte_length(attr_val); + + suffix_start = (char *)(avdata + avdata_len - suffixlen); + + if ((avdata_len >= suffixlen) && + (strncasecmp(suffix_start, + lwc_string_data(suffix), + suffixlen) == 0)) { + /* Use strncasecmp to compare only the last + * "n" characters, where "n" is the length of + * the suffix. */ + *match = true; + } + } + + dom_string_unref(attr_val); + + return CSS_OK; +} + + +/** + * Implement node_has_attribute_substring() with optional case- + * insensitivity. This corresponds to [attr*=value i] in CSS and is + * not supported by libcss yet, but it allows us to factor out some + * common code. + */ +static css_error _node_has_attribute_substring(void *pw, void *node, + const css_qname *qname, lwc_string *substring, + bool *match, bool insensitive) +{ + UNUSED(pw); + dom_string *name; + dom_string *attr_val; + dom_exception err; + size_t attr_len; /* length of attr_val */ + size_t substrlen; /* length of "substring" */ + + /* Convenience pointers we use when comparing substrings */ + const char *p; + const char *p_max; + + substrlen = lwc_string_length(substring); + if (substrlen == 0) { + /* In this case, the spec says that "if 'val' is the + * empty string, it will never represent anything." */ + return CSS_OK; + } + + /* Intern the attribute name as a dom_string so we can + * use dom_element_get_attribute() */ + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), + &name); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + err = dom_element_get_attribute((dom_node *)node, name, &attr_val); + if ((err != DOM_NO_ERR) || (attr_val == NULL)) { + /* There was an error getting the attribute's value or + * the attribute doesn't exist. So, no match? */ + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + /* Otherwise, we have the attribute value from the given node, + * and the first thing we want to do is check to see if the + * whole thing matches the substring. */ + dom_string_unref(name); + + if (insensitive) { + *match = dom_string_caseless_lwc_isequal(attr_val, substring); + } + else { + *match = dom_string_lwc_isequal(attr_val, substring); + } + + /* If not, check to see if an, uh, substring matches the + * substring */ + if (*match == false) { + p = dom_string_data(attr_val); + + /* Check every long-enough suffix for a prefix match */ + attr_len = dom_string_byte_length(attr_val); + if (attr_len >= substrlen) { + p_max = p + attr_len - substrlen; + while (p <= p_max) { + if (strncasecmp(p, + lwc_string_data(substring), + substrlen) == 0) { + *match = true; + break; + } + p++; + } + } + } + + dom_string_unref(attr_val); + + return CSS_OK; +} + +/** + * Test the given node for an attribute whose value contains the + * given substring + * + * This will return true (via the "match" pointer) if the libdom node + * has an attribute with the given name and whose value contains the + * given substring. The comparison is case-sensitive. This corresponds + * to [attr*=value] in CSS. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param qname Attribute name to check for + * \param substring Value substring to check for + * \param match Pointer to the test result + * + * \return Returns CSS_OK if successful and CSS_NOMEM if we cannot + * intern the attribute name (which usually indicates memory + * exhaustion) + */ +css_error node_has_attribute_substring(void *pw, void *node, + const css_qname *qname, lwc_string *substring, + bool *match) +{ + return _node_has_attribute_substring(pw, node, qname, substring, + match, false); +} + + +/** + * Test whether or not the given node is the document's root element + * This corresponds to the CSS :root pseudo-selector. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to test + * \param match Pointer to the test result + * + * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong + */ +css_error node_is_root(void *pw, void *node, bool *match) +{ + UNUSED(pw); + dom_node *parent; + dom_node_type type; + dom_exception err; + + err = dom_node_get_parent_node((dom_node *)node, &parent); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + /* It's the root element if it doesn't have a parent element */ + if (parent != NULL) { + err = dom_node_get_node_type(parent, &type); + dom_node_unref(parent); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + if (type != DOM_DOCUMENT_NODE) { + /* DOM_DOCUMENT_NODE is the only allowable + * type of parent node for the root element */ + *match = false; + return CSS_OK; + } + } + + *match = true; + return CSS_OK; +} + + +/** + * Used internally in node_count_siblings() to "count" the given + * sibling node. It factors out the node type and name checks. + */ +static int node_count_siblings_check(dom_node *dnode, + bool check_name, + dom_string *name) +{ + int ret; + dom_node_type type; + dom_exception exc; + dom_string *dnode_name; + + /* We flip this to 1 if/when we count this node */ + ret = 0; + + if (dnode == NULL) { + return ret; + } + + exc = dom_node_get_node_type(dnode, &type); + if ((exc != DOM_NO_ERR) || (type != DOM_ELEMENT_NODE)) { + /* We only count element siblings */ + return ret; + } + + /* ... with the right name */ + if (check_name) { + dnode_name = NULL; + exc = dom_node_get_node_name(dnode, &dnode_name); + + if ((exc == DOM_NO_ERR) && (dnode_name != NULL)) { + if (dom_string_isequal(name, + dnode_name)) { + ret = 1; + } + dom_string_unref(dnode_name); + } + } + else { + ret = 1; + } + + return ret; +} + +/** + * Count the given node's sibling elements + * + * This counts the given node's sibling elements in one direction, + * either forwards or backwards, in the DOM. Keep in mind that the + * libdom tree is upside-down compared to the CSS one; so "next" and + * "previous" are actually reversed; the default is to count preceding + * libdom siblings which correspond to subsequent CSS siblings. + * + * This operation is central to the CSS :first-child, :nth-child, and + * :last-child (et cetera) pseudo-selectors. + * + * If same_name is true, then only nodes having the same + * (case-sensitive) name as the given node are counted. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node whose siblings we're counting + * \param same_name Whether or not to count only siblings having + * the same name as the given node + * \param after Count subsequent siblings rather than precedent + * ones (the default) + * \param count Pointer to the return value, the number of sibling + * elements + * + * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong + */ +css_error node_count_siblings(void *pw, void *node, + bool same_name, bool after, int32_t *count) +{ + UNUSED(pw); + dom_exception exc; + dom_node *dnode; /* node, but with the right type */ + dom_string *dnode_name; + dom_node *next; /* "next" sibling (depends on direction) */ + + /* Pointer to the "next sibling" function */ + dom_exception (*next_func)(dom_node *, dom_node **); + + *count = 0; + + dnode_name = NULL; + dnode = (dom_node *)node; + if (same_name) { + exc = dom_node_get_node_name(dnode, &dnode_name); + if ((exc != DOM_NO_ERR) || (dnode_name == NULL)) { + return CSS_NOMEM; + } + } + + /* Increment the reference counter for dnode for as long as + * we retain a reference to it. */ + dnode = dom_node_ref(dnode); + + next_func = dom_node_get_previous_sibling; + if (after) { + next_func = dom_node_get_next_sibling; + } + + do { + exc = next_func(dnode, &next); + if (exc != DOM_NO_ERR) { + break; + } + + /* If next_func worked, we're about to swap "next" + * with "dnode" meaning that we will no longer retain + * a reference to the current dnode. */ + dom_node_unref(dnode); + dnode = next; + + *count += node_count_siblings_check(dnode, + same_name, + dnode_name); + } while (dnode != NULL); + + if (dnode_name != NULL) { + dom_string_unref(dnode_name); + } + + return CSS_OK; +} + + +/** + * Determine whether or not the given element is empty + * + * An element is "nonempty" if it has a child that is either an + * element node or a text node. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to check for emptiness + * \param is_empty Pointer to the return value + * + * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong + */ +css_error node_is_empty(void *pw, void *node, bool *is_empty) +{ + UNUSED(pw); + dom_node *child; /* current child node pointer */ + dom_node *next; /* next child node pointer */ + dom_node_type type; /* what type of node is "child" */ + dom_exception err; + + /* Assume that it's empty by default */ + *is_empty = true; + + /* Get the given node's first child. Implementation detail: + * this increments the reference counter on the child node. */ + err = dom_node_get_first_child((dom_node *)node, &child); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + /* And now loop through all children looking for a + * text/element node. If we find one, the original + * node is "nonempty" */ + while (child != NULL) { + err = dom_node_get_node_type(child, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(child); + return CSS_NOMEM; + } + + if (type == DOM_ELEMENT_NODE || type == DOM_TEXT_NODE) { + *is_empty = false; + dom_node_unref(child); + return CSS_OK; + } + + err = dom_node_get_next_sibling(child, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(child); + return CSS_NOMEM; + } + + /* If we're moving to the next node, we can release + * the reference to the current one */ + dom_node_unref(child); + child = next; + } + + return CSS_OK; +} + + +/** + * Determine whether or not the given node is a link + * + * A node is a link if it is an element node whose name is "a" and if + * it has an "href" attribute (case-sensitive). This selector + * corresponds to node:link pseudo-class in CSS. + * + * This pseudo-class is a bit awkward because the two standards (HTML5 + * and CSS) disagree on what it means, and because libsvgtiny does not + * have enough information to determine if a link has been "visited" + * yet -- that's a UI property. CSS says that :link is for unvisited + * links, which we can't determine. HTML5 says that each link must + * be either a :link or :visited. Since we can't decide either way, + * It seems less wrong to declare that all links are unvisited; i.e. + * that they match :link. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node to check + * \param is_link Pointer to the boolean return value + * + * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong + */ +css_error node_is_link(void *pw, void *node, bool *is_link) +{ + dom_exception exc; + dom_node *dnode; /* node, but with the right type */ + dom_string *dnode_name; + bool has_href; + struct svgtiny_parse_state* state; + + dnode = (dom_node *)node; + dnode_name = NULL; + has_href = false; /* assume no href attribute */ + *is_link = false; /* assume that it's not a link */ + + exc = dom_node_get_node_name(dnode, &dnode_name); + if ((exc != DOM_NO_ERR) || (dnode_name == NULL)) { + return CSS_NOMEM; + } + + state = (struct svgtiny_parse_state *)pw; + if (dom_string_isequal(dnode_name, state->interned_a)) { + exc = dom_element_has_attribute(node, + state->interned_href, + &has_href); + if (exc == DOM_NO_ERR && has_href) { + *is_link = true; + } + } + + dom_string_unref(dnode_name); + return CSS_OK; +} + +/** + * Check if the given node is a link that has been visited already + * + * This check always fails because the SVG DOM does not have the + * necessary information (it's a UI property). + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check; unused + * \param is_visited Pointer to the boolean return value + * + * \return Always returns CSS_OK + */ +css_error node_is_visited(void *pw, void *node, bool *is_visited) +{ + UNUSED(pw); + UNUSED(node); + *is_visited = false; + return CSS_OK; +} + + +/** + * Check if the given node is being "hovered" over + * + * This check always fails because the SVG DOM does not have the + * necessary information (it's a UI property). + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check; unused + * \param is_hover Pointer to the boolean return value + * + * \return Always returns CSS_OK + */ +css_error node_is_hover(void *pw, void *node, bool *is_hover) +{ + UNUSED(pw); + UNUSED(node); + *is_hover = false; + return CSS_OK; +} + + +/** + * Check if the given node is "active" + * + * This check always fails because the SVG DOM does not have the + * necessary information (it's a UI property). + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check; unused + * \param is_active Pointer to the boolean return value + * + * \return Always returns CSS_OK + */ +css_error node_is_active(void *pw, void *node, bool *is_active) +{ + UNUSED(pw); + UNUSED(node); + *is_active = false; + return CSS_OK; +} + + +/** + * Check if the given node has the focus + * + * This check always fails because the SVG DOM does not have the + * necessary information (it's a UI property). + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check; unused + * \param is_focus Pointer to the boolean return value + * + * \return Always returns CSS_OK + */ +css_error node_is_focus(void *pw, void *node, bool *is_focus) +{ + UNUSED(pw); + UNUSED(node); + *is_focus = false; + return CSS_OK; +} + + +/** + * Check if the given node is enabled + * + * This check always fails because the SVG DOM does not have the + * necessary information (it's a UI property). + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check; unused + * \param is_enabled Pointer to the boolean return value + * + * \return Always returns CSS_OK + */ +css_error node_is_enabled(void *pw, void *node, bool *is_enabled) +{ + UNUSED(pw); + UNUSED(node); + *is_enabled = false; + return CSS_OK; +} + + +/** + * Check if the given node is disabled + * + * This check always fails because the SVG DOM does not have the + * necessary information (it's a UI property). Beware, until they are + * implemented, this is NOT the logical negation of node_is_enabled! + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check; unused + * \param is_disabled Pointer to the boolean return value + * + * \return Always returns CSS_OK + */ +css_error node_is_disabled(void *pw, void *node, bool *is_disabled) +{ + UNUSED(pw); + UNUSED(node); + *is_disabled = false; + return CSS_OK; +} + + +/** + * Test whether or not the given node is "checked" + * + * This test always fails because the SVG DOM does not have the + * necessary information (it's a UI property). + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check; unused + * \param is_checked Pointer to the boolean return value + * + * \return Always returns CSS_OK + */ +css_error node_is_checked(void *pw, void *node, bool *is_checked) +{ + UNUSED(pw); + UNUSED(node); + *is_checked = false; + return CSS_OK; +} + + +/** + * Check if the given node is the "target" of the document URL + * + * This test always fails because the SVG DOM does not have the + * necessary information (it's a UI property). + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check; unused + * \param is_target Pointer to the boolean return value + * + * \return Always returns CSS_OK + */ +css_error node_is_target(void *pw, void *node, bool *is_target) +{ + UNUSED(pw); + UNUSED(node); + *is_target = false; + return CSS_OK; +} + + +/** + * Check if the given node is the given language + * + * This test is corresponds to the CSS :lang() selector and is not + * fully implemented yet: it looks only for "lang" attributes on the + * given element and its parents, and performs a simple substring + * check. This results in a partial implementation of CSS Level 3 for + * SVG 2.0. In particular, it ignores all "xml:lang" attributes in + * favor of the "lang" attribute that is defined only in SVG 2.0. + * + * \param pw Pointer to the current SVG parser state; unused + * \param node Libdom SVG node to check + * \param lang The language to match + * \param is_lang Pointer to the boolean return value + * + * \return CSS_OK on success, or CSS_NOMEM if anything goes wrong + */ +static css_error node_is_lang(void *pw, void *node, + lwc_string *lang, bool *is_lang) +{ + UNUSED(pw); + /* SVG2 elements support both "lang" and "xml:lang" + * attributes; earlier versions have only the XML + * attribute. It would not be too hard to add support for + * xml:lang" here. The main difficulty standing in the way of + * a full Level 4 implementation is the complexity of the + * :lang() selector: + * + * https://www.w3.org/TR/selectors-4/#the-lang-pseudo + * + */ + + css_error c_err; + dom_exception d_err; + dom_node *n; /* current node */ + dom_node *p; /* parent node */ + bool match; /* retval from node_has_attribute_substring() */ + + /* Define the attribute name "lang" that we're looking for. + * We only use a css_qname here because that's what the + * node_has_attribute_substring() takes; the namespace + * portion of it is irrelevant. */ + css_qname attr; + attr.ns = NULL; + + if (lwc_intern_string("lang", 4, &attr.name) != lwc_error_ok) { + return CSS_NOMEM; + } + + *is_lang = false; /* default to no match */ + n = (dom_node *)node; + + /* Loop through all parents of the given node looking for a + * substring match */ + while (n != NULL) { + c_err = _node_has_attribute_substring(pw, (void *)n, &attr, + lang, &match, true); + if (c_err != CSS_OK) { + lwc_string_destroy(attr.name); + return c_err; + } + if (match) { + /* matched this element; we're done */ + lwc_string_destroy(attr.name); + *is_lang = true; + return CSS_OK; + } + + /* no match on this element, try its parent */ + d_err = dom_node_get_parent_node(n, &p); + if (d_err != DOM_NO_ERR) { + lwc_string_destroy(attr.name); + return CSS_NOMEM; + } + n = p; + } + + /* If we never find a match we may wind up here */ + lwc_string_destroy(attr.name); + return CSS_OK; +} + + +/** + * User-agent defaults for CSS properties + * + * For the moment, we provide no defaults, because libsvgtiny does not + * yet support any CSS properties that might need them. + * + * \param pw Pointer to the current SVG parser state; unused + * \param property LibCSS property identifier; unused + * \param hint Pointer to hint object (a return value); unused + * + * \return Always returns CSS_INVALID + */ +css_error ua_default_for_property(void *pw, uint32_t property, + css_hint *hint) +{ + UNUSED(pw); + UNUSED(property); + UNUSED(hint); + return CSS_INVALID; +} + + +/** + * Store libcss data on a node + * + * This is part of the libcss select handler API that we need to + * implement. It is essentially a thin dom_node_set_user_data() + * wrapper. + * + * \param pw Pointer to the current SVG parser state + * \param node Libdom SVG node on which to store the data + * \param libcss_node_data Pointer to the data to store + * + * \return Always returns CSS_OK + */ +css_error set_libcss_node_data(void *pw, void *node, + void *libcss_node_data) +{ + struct svgtiny_parse_state *state; + void *old_data; + + /* A unique "userdata key" (a string) is used to identify this + * data. The fourth parameter (NULL) is a "user handler + * function." We will eventually have one, but for now we set + * it to NULL to avoid a circular reference mess that would + * break the build temporarily. */ + state = (struct svgtiny_parse_state *)pw; + dom_node_set_user_data((dom_node *)node, + state->interned_userdata_key, + libcss_node_data, + NULL, + &old_data); + + /* dom_node_set_user_data() always returns DOM_NO_ERR */ + return CSS_OK; +}