diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core/Makefile | 2 | ||||
-rw-r--r-- | src/core/tokenlist.c | 547 | ||||
-rw-r--r-- | src/core/tokenlist.h | 18 | ||||
-rw-r--r-- | src/html/html_element.c | 4 | ||||
-rw-r--r-- | src/utils/walk.c | 12 |
5 files changed, 574 insertions, 9 deletions
diff --git a/src/core/Makefile b/src/core/Makefile index 41fd51f..5d9c969 100644 --- a/src/core/Makefile +++ b/src/core/Makefile @@ -6,6 +6,6 @@ DIR_SOURCES := \ text.c typeinfo.c comment.c \ namednodemap.c nodelist.c \ cdatasection.c document_type.c entity_ref.c pi.c \ - doc_fragment.c document.c + doc_fragment.c document.c tokenlist.c include $(NSBUILD)/Makefile.subdir diff --git a/src/core/tokenlist.c b/src/core/tokenlist.c new file mode 100644 index 0000000..cc93a8c --- /dev/null +++ b/src/core/tokenlist.c @@ -0,0 +1,547 @@ +/* + * This file is part of libdom. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2022 Daniel Silverstone <dsilvers@digital-scurf.org> + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <dom/core/element.h> +#include <dom/core/nodelist.h> +#include <dom/core/tokenlist.h> +#include <dom/core/string.h> +#include <dom/events/event.h> +#include <dom/events/event_target.h> +#include <dom/events/event_listener.h> +#include <dom/events/mutation_event.h> + +#include "core/element.h" +#include "core/document.h" + +#include "utils/utils.h" + +#define DOM_TOKENLIST_GROW_INCREMENT 4 + +struct dom_tokenlist { + uint32_t refcnt; + dom_element *ele; + dom_string *attr; + dom_event_listener *listener; + dom_string *last_set; + bool needs_parse; + /* Parsed content, for optimal access */ + dom_string **entries; + uint32_t len; + uint32_t alloc; +}; + +/* Handle a DOMAttrModified event which might be to do with our attribute */ + +static void _dom_tokenlist_handle_attrmodified(dom_event *evt, void *pw) +{ + dom_mutation_event *mutevt = (dom_mutation_event *)evt; + dom_tokenlist *list = (dom_tokenlist *)pw; + dom_exception exc; + dom_string *value; + + { + dom_event_target *target; + exc = dom_event_get_target(evt, &target); + if (exc != DOM_NO_ERR) + return; + dom_node_unref(target); + if (target != (dom_event_target *)list->ele) + return; + } + + { + dom_string *attr; + exc = dom_mutation_event_get_attr_name(mutevt, &attr); + if (exc != DOM_NO_ERR) + return; + if (!dom_string_isequal(attr, list->attr)) { + dom_string_unref(attr); + return; + } + dom_string_unref(attr); + } + + /* At this point we know that this is a mutation of our attribute on our + * node */ + + exc = dom_mutation_event_get_new_value(mutevt, &value); + if (exc != DOM_NO_ERR) + return; + + if (list->last_set != NULL && + dom_string_isequal(list->last_set, value)) { + /* We've just seen the mutation event for one of our own set + * operations */ + dom_string_unref(value); + return; + } + + /* Mark that we need to re-parse the tokenlist on the next request */ + list->needs_parse = true; + + dom_string_unref(value); +} + +static dom_exception _dom_tokenlist_make_room(dom_tokenlist *list) +{ + if (list->len == list->alloc) { + uint32_t new_alloc = list->alloc + DOM_TOKENLIST_GROW_INCREMENT; + dom_string **new_entries = realloc( + list->entries, new_alloc * sizeof(dom_string *)); + if (new_entries == NULL) + return DOM_NO_MEM_ERR; + list->alloc = new_alloc; + list->entries = new_entries; + } + + return DOM_NO_ERR; +} + +static dom_exception _dom_tokenlist_reparse(dom_tokenlist *list) +{ + dom_exception exc; + dom_string *value; + const char *pos; + uint32_t remaining, check; + uint32_t n_entries = 0; + dom_string *temp; + bool found; + + if (!list->needs_parse) + return DOM_NO_ERR; + + /* Clean down the current entries */ + while (list->len-- > 0) + dom_string_unref(list->entries[list->len]); + list->len = 0; + + /* Get the "new" attribute value */ + exc = dom_element_get_attribute(list->ele, list->attr, &value); + if (exc != DOM_NO_ERR) + return exc; + + /* If there is no value, we're an empty list and we're done */ + if (value == NULL) { + list->needs_parse = false; + return DOM_NO_ERR; + } + + /* OK, there's something here to do, so let's do it... */ + + /* Count number of entries */ + for (pos = dom_string_data(value), remaining = dom_string_length(value); + remaining > 0;) { + if (*pos != ' ') { + while (*pos != ' ' && remaining > 0) { + remaining--; + pos++; + } + n_entries++; + } else { + while (*pos == ' ' && remaining > 0) { + remaining--; + pos++; + } + } + } + + /* If there are no entries (all whitespace) just bail here */ + if (n_entries == 0) { + list->needs_parse = false; + dom_string_unref(value); + return DOM_NO_ERR; + } + + /* If we need more room, reallocate the buffer */ + if (list->alloc < n_entries) { + dom_string **new_alloc = realloc( + list->entries, n_entries * sizeof(dom_string *)); + if (new_alloc == NULL) { + dom_string_unref(value); + return DOM_NO_MEM_ERR; + } + list->entries = new_alloc; + list->alloc = n_entries; + } + + /* And now parse those entries into the buffer */ + for (pos = dom_string_data(value), + remaining = dom_string_length(value), + n_entries = 0; + remaining > 0;) { + if (*pos != ' ') { + const char *s = pos; + while (*pos != ' ' && remaining > 0) { + pos++; + remaining--; + } + exc = dom_string_create_interned((const uint8_t *)s, + pos - s, + &temp); + if (exc != DOM_NO_ERR) { + dom_string_unref(value); + return exc; + } + found = false; + for (check = 0; check < list->len; check++) { + if (dom_string_isequal(temp, + list->entries[check])) { + found = true; + break; + } + } + if (found == true) { + dom_string_unref(temp); + } else { + list->entries[list->len] = temp; + list->len++; + } + } else { + while (*pos == ' ' && remaining > 0) { + pos++; + remaining--; + } + } + } + + dom_string_unref(value); + list->needs_parse = false; + + return DOM_NO_ERR; +} + +static dom_exception _dom_tokenlist_reify(dom_tokenlist *list) +{ + dom_exception exc; + uint32_t nchars = 0, n; + char *buffer, *next; + dom_string *output; + + if (list->len == 0) { + if (list->last_set != NULL) { + dom_string_unref(list->last_set); + } + list->last_set = dom_string_ref( + list->ele->base.owner->_memo_empty); + return dom_element_set_attribute(list->ele, + list->attr, + list->last_set); + } + + for (n = 0; n < list->len; ++n) + nchars += dom_string_length(list->entries[n]); + + buffer = calloc(1, nchars + list->len); + if (buffer == NULL) + return DOM_NO_MEM_ERR; + + for (next = buffer, n = 0; n < list->len; ++n) { + uint32_t slen = dom_string_length(list->entries[n]); + memcpy(next, dom_string_data(list->entries[n]), slen); + next[slen] = ' '; + next += slen + 1; + } + + exc = dom_string_create_interned((const uint8_t *)buffer, + nchars + list->len - 1, + &output); + free(buffer); + if (exc != DOM_NO_ERR) + return exc; + + if (list->last_set != NULL) { + dom_string_unref(list->last_set); + } + list->last_set = output; + + return dom_element_set_attribute(list->ele, list->attr, list->last_set); +} + +/**********************************************************************************/ + +/** + * Create a tokenlist + * + * \param ele The element which owns the tokenlist attribute + * \param attr The name of the attribute we are treating as a tokenlist + * \param list The tokenlist output which is set on success + * \return DOM_NO_ERR on success, DOM_NO_MEM_ERR on memory exhaustion + * + * The returned list will already be referenced, so the client need not + * do so explicitly. The client must unref the list once finished with it. + * + * This list will take its own references to ::ele and ::attr + */ +dom_exception +dom_tokenlist_create(dom_element *ele, dom_string *attr, dom_tokenlist **list) +{ + dom_tokenlist *l; + dom_exception exc; + + l = calloc(1, sizeof(dom_tokenlist)); + if (l == NULL) + return DOM_NO_MEM_ERR; + + l->refcnt = 1; + l->ele = (dom_element *)dom_node_ref(ele); + l->attr = dom_string_ref(attr); + l->needs_parse = true; + + exc = dom_event_listener_create(_dom_tokenlist_handle_attrmodified, + l, + &l->listener); + if (exc != DOM_NO_ERR) + goto fail; + + exc = dom_event_target_add_event_listener( + ele, + ele->base.owner->_memo_domattrmodified, + l->listener, + false); + + if (exc != DOM_NO_ERR) + goto fail; + + *list = l; + + return DOM_NO_ERR; + +fail: + if (l->listener != NULL) + dom_event_listener_unref(l->listener); + dom_node_unref(l->ele); + dom_string_unref(l->attr); + free(l); + return exc; +} + +/** + * Claim a ref on a tokenlist + * + * \param list The tokenlist to claim a ref on + */ +void dom_tokenlist_ref(dom_tokenlist *list) +{ + assert(list != NULL); + list->refcnt++; +} + +/** + * Release a ref on a tokenlist + * + * \param list The list to release the reference of + * + * If you release the last ref, this cleans up the tokenlist + */ +void dom_tokenlist_unref(dom_tokenlist *list) +{ + assert(list != NULL); + + if (--list->refcnt > 0) + return; + + if (list->alloc > 0) { + while (list->len-- > 0) + dom_string_unref(list->entries[list->len]); + free(list->entries); + } + + dom_event_target_remove_event_listener( + list->ele, + list->ele->base.owner->_memo_domattrmodified, + list->listener, + false); + + dom_event_listener_unref(list->listener); + + if (list->last_set != NULL) + dom_string_unref(list->last_set); + + dom_string_unref(list->attr); + dom_node_unref(list->ele); + + free(list); +} + +/** + * Get the length of the tokenlist + * + * \param list The list to get the length of + * \param length Length of the list outputs here + * \return DOM_NO_ERR on success, otherwise the failure code + */ +dom_exception dom_tokenlist_get_length(dom_tokenlist *list, uint32_t *length) +{ + dom_exception exc; + assert(list != NULL); + + exc = _dom_tokenlist_reparse(list); + if (exc != DOM_NO_ERR) + return exc; + + *length = list->len; + + return DOM_NO_ERR; +} + +/** + * Get a particular item from the tokenlist + * + * \param list The list to retrieve the item from + * \param index The index of the item to retrieve + * \param value The value of the item returns here + * \return DOM_NO_ERR on success, otherwise the failure code + */ +dom_exception +_dom_tokenlist_item(dom_tokenlist *list, uint32_t index, dom_string **value) +{ + dom_exception exc; + assert(list != NULL); + + exc = _dom_tokenlist_reparse(list); + if (exc != DOM_NO_ERR) + return exc; + + if (index >= list->len) { + *value = NULL; + return DOM_NO_ERR; + } + + *value = dom_string_ref(list->entries[index]); + return DOM_NO_ERR; +} + +/** + * Retrieve the value of the tokenlist as a string + * + * \param list The list to retrieve the value of + * \param value The value of the list returns here + * \return DOM_NO_ERR on success, otherwise the failure code + */ +dom_exception dom_tokenlist_get_value(dom_tokenlist *list, dom_string **value) +{ + assert(list != NULL); + + return dom_element_get_attribute(list->ele, list->attr, value); +} + +/** + * Set the value of the tokenlist as a string + * + * \param list The list to set the value of + * \param value The value to set + * \return DOM_NO_ERR on success, otherwise the failure code + * + */ +dom_exception dom_tokenlist_set_value(dom_tokenlist *list, dom_string *value) +{ + assert(list != NULL); + + return dom_element_set_attribute(list->ele, list->attr, value); +} + +/** + * Check if the given value is in the tokenlist + * + * \param list The list to scan for the given value + * \param value The value to look for in the token list + * \param contains This will be set based on whether or not the value is present + * \return DOM_NO_ERR on success, otherwise the failure code + */ +dom_exception +dom_tokenlist_contains(dom_tokenlist *list, dom_string *value, bool *contains) +{ + dom_exception exc; + uint32_t n; + + assert(list != NULL); + + exc = _dom_tokenlist_reparse(list); + if (exc != DOM_NO_ERR) + return exc; + + *contains = false; + + for (n = 0; n < list->len; n++) { + if (dom_string_isequal(value, list->entries[n])) { + *contains = true; + break; + } + } + + return DOM_NO_ERR; +} + +/** + * Add the given value to the tokenlist + * + * \param list The list to add to + * \param value The value to add + * \return DOM_NO_ERR on success, otherwise the failure code + */ +dom_exception dom_tokenlist_add(dom_tokenlist *list, dom_string *value) +{ + dom_exception exc; + bool present = false; + + assert(list != NULL); + + exc = dom_tokenlist_contains(list, value, &present); + if (exc != DOM_NO_ERR) + return exc; + + if (present == true) + return DOM_NO_ERR; + + exc = _dom_tokenlist_make_room(list); + if (exc != DOM_NO_ERR) + return exc; + + list->entries[list->len++] = dom_string_ref(value); + + exc = _dom_tokenlist_reify(list); + + return exc; +} + +/** + * Remove the given value from the tokenlist + * + * \param list The list to remove from + * \param value The value to remove + * \return DOM_NO_ERR on success, otherwise the failure code + */ +dom_exception dom_tokenlist_remove(dom_tokenlist *list, dom_string *value) +{ + dom_exception exc; + uint32_t n, m; + + assert(list != NULL); + + exc = _dom_tokenlist_reparse(list); + if (exc != DOM_NO_ERR) + return false; + + for (n = 0; n < list->len; ++n) { + if (dom_string_isequal(value, list->entries[n])) { + dom_string_unref(list->entries[n]); + for (m = n + 1; m < list->len; ++m) { + list->entries[m - 1] = list->entries[m]; + } + list->len--; + break; + } + } + + exc = _dom_tokenlist_reify(list); + + return exc; +} diff --git a/src/core/tokenlist.h b/src/core/tokenlist.h new file mode 100644 index 0000000..325d726 --- /dev/null +++ b/src/core/tokenlist.h @@ -0,0 +1,18 @@ +/* + * This file is part of libdom. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org> + */ + +#ifndef dom_internal_core_tokenlist_h_ +#define dom_internal_core_tokenlist_h_ + +#include <stdbool.h> + +#include <dom/core/tokenlist.h> + +struct dom_element; +struct dom_string; + +#endif diff --git a/src/html/html_element.c b/src/html/html_element.c index 3062a23..c110455 100644 --- a/src/html/html_element.c +++ b/src/html/html_element.c @@ -518,7 +518,7 @@ dom_exception dom_html_element_set_int32_t_property(dom_html_element *ele, if (err != DOM_NO_ERR) goto fail; - if (snprintf(numbuffer, 32, "%u", value) == 32) + if (snprintf(numbuffer, 32, "%"PRIu32, value) == 32) numbuffer[31] = '\0'; err = dom_string_create((const uint8_t *) numbuffer, @@ -607,7 +607,7 @@ dom_exception dom_html_element_set_dom_ulong_property(dom_html_element *ele, if (err != DOM_NO_ERR) goto fail; - if (snprintf(numbuffer, 32, "%u", value) == 32) + if (snprintf(numbuffer, 32, "%"PRIu32, value) == 32) numbuffer[31] = '\0'; err = dom_string_create((const uint8_t *) numbuffer, diff --git a/src/utils/walk.c b/src/utils/walk.c index 20314f3..f103908 100644 --- a/src/utils/walk.c +++ b/src/utils/walk.c @@ -19,7 +19,7 @@ * \param[in] stage Whether the \ref node is being entered or left. * \param[in] node The node being walked. * \param[in] cb The client callback function. - * \param[in] ctx The client's private data. + * \param[in] pw The client's private data. * \param[out] cmd_out Walk instruction from client. * \return false for early termination of walk, true otherwise. */ @@ -28,7 +28,7 @@ static inline dom_exception dom_walk__cb( enum dom_walk_stage stage, dom_node *node, dom_walk_cb cb, - void *ctx, + void *pw, enum dom_walk_cmd *cmd_out) { if ((1 << stage) & mask) { @@ -40,7 +40,7 @@ static inline dom_exception dom_walk__cb( return exc; } - *cmd_out = cb(stage, type, node, ctx); + *cmd_out = cb(stage, type, node, pw); } return DOM_NO_ERR; @@ -51,7 +51,7 @@ dom_exception libdom_treewalk( enum dom_walk_enable mask, dom_walk_cb cb, dom_node *root, - void *ctx) + void *pw) { dom_node *node; dom_exception exc; @@ -77,7 +77,7 @@ dom_exception libdom_treewalk( /* No children; siblings & ancestor's siblings */ while (node != root) { exc = dom_walk__cb(mask, DOM_WALK_STAGE_LEAVE, - node, cb, ctx, &cmd); + node, cb, pw, &cmd); if (exc != DOM_NO_ERR || cmd == DOM_WALK_CMD_ABORT) { dom_node_unref(node); @@ -118,7 +118,7 @@ dom_exception libdom_treewalk( assert(node != root); exc = dom_walk__cb(mask, DOM_WALK_STAGE_ENTER, node, - cb, ctx, &cmd); + cb, pw, &cmd); if (exc != DOM_NO_ERR) { return exc; } |