From e28c8c517ef1e89638de059c5f8da6cb8dab0adc Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Sat, 20 Jan 2018 10:22:55 +0000 Subject: Add HTMLFormControlsCollection and RadioNodeList These are a necessary step on the way to libdom being able to manage the content and behaviours of web forms. --- src/html/html_radio_nodelist.c | 419 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 src/html/html_radio_nodelist.c (limited to 'src/html/html_radio_nodelist.c') diff --git a/src/html/html_radio_nodelist.c b/src/html/html_radio_nodelist.c new file mode 100644 index 0000000..52e25f1 --- /dev/null +++ b/src/html/html_radio_nodelist.c @@ -0,0 +1,419 @@ +/* + * This file is part of libdom. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2018 Daniel Silverstone + */ + +#include +#include + +#include + +#include +#include + +#include "html/html_radio_nodelist.h" +#include "html/html_form_controls_collection.h" +#include "html/html_document.h" +#include "html/html_form_element.h" + +#include "core/node.h" +#include "core/element.h" +#include "core/string.h" + +/*-----------------------------------------------------------------------*/ +/* Internal functions */ + +/* Callback function to test whether certain node is a form control, see + * src/html/html_collection.h for detail. */ +static bool _dom_is_specific_form_control(struct dom_node_internal *node, void *ctx) +{ + struct dom_html_document *doc = + (struct dom_html_document *)(node->owner); + struct dom_html_radio_nodelist *nodelist = ctx; + dom_string *str; + dom_exception exc; + + if (_dom_is_form_control(node, nodelist->form)) { + /* It's the control we care about if id or name are equal + * to nodelist->name. We wouldn't be here if node wasn't + * an element node. + */ + exc = _dom_element_get_id((struct dom_element *)node, &str); + + if (exc != DOM_NO_ERR) { + return false; + } + + if (str != NULL && dom_string_isequal(str, nodelist->name)) { + dom_string_unref(str); + return true; + } + + if (str != NULL) + dom_string_unref(str); + + exc = _dom_element_get_attribute((struct dom_element *)node, + doc->memoised[hds_name], &str); + + if (str != NULL && dom_string_isequal(str, nodelist->name)) { + dom_string_unref(str); + return true; + } + + if (str != NULL) + dom_string_unref(str); + } + + return false; +} + +/** + * Create a dom_html_radio_nodelist + * + * \param form The HTML form element for the radio nodelist + * \param name The element id/name for the radio nodelist + * \param nodelist The result radio nodelist object + * \return DOM_NO_ERR on success, appropriate dom_exception on failure. + */ +dom_exception _dom_html_radio_nodelist_create(struct dom_html_form_element *form, + dom_string *name, + struct dom_html_radio_nodelist **nodelist) +{ + dom_exception exc; + *nodelist = malloc(sizeof(struct dom_html_radio_nodelist)); + + if (*nodelist == NULL) + return DOM_NO_MEM_ERR; + + exc = _dom_html_radio_nodelist_initialise(form, *nodelist, name); + + if (exc != DOM_NO_ERR) { + /* This is safe because we only ever get an error before we + * ref anything which would need finalising + */ + free(*nodelist); + *nodelist = NULL; + } + + return exc; +} + +/** + * Initialise a dom_html_radio_nodelist + * + * \param form The HTML form element for the radio nodelist + * \param nodelist The radio nodelist object to initialise + * \param name The element id/name for the radio nodelist + * \return DOM_NO_ERR on success, appropriate dom_exception on failure. + */ +dom_exception _dom_html_radio_nodelist_initialise(struct dom_html_form_element *form, + struct dom_html_radio_nodelist *nodelist, + dom_string *name) +{ + dom_exception exc; + dom_html_document *doc = (dom_html_document *) dom_node_get_owner(form); + + assert(form != NULL); + assert(name != NULL); + + assert(doc != NULL); + + exc = _dom_html_collection_create(doc, + (dom_node_internal *) doc, + _dom_is_specific_form_control, + nodelist, &nodelist->col); + + if (exc != DOM_NO_ERR) + return exc; + + nodelist->form = form; + dom_node_ref(form); + + nodelist->name = name; + dom_string_ref(name); + + nodelist->refcnt = 1; + + return DOM_NO_ERR; +} + +/** + * Finalise a dom_html_radio_nodelist object + * + * \param nodelist The dom_html_radio_nodelist object + */ +void _dom_html_radio_nodelist_finalise(struct dom_html_radio_nodelist *nodelist) +{ + assert(nodelist->refcnt == 0); + dom_node_unref(nodelist->form); + nodelist->form = NULL; + dom_string_unref(nodelist->name); + nodelist->name = NULL; + dom_html_collection_unref(nodelist->col); + nodelist->col = NULL; +} + +/** + * Destroy a dom_html_radio_nodelist object + * + * \param nodelist The dom_html_radio_nodelist object + */ +void _dom_html_radio_nodelist_destroy(struct dom_html_radio_nodelist *nodelist) +{ + _dom_html_radio_nodelist_finalise(nodelist); + free(nodelist); +} + +/*-----------------------------------------------------------------------*/ +/* Public API */ + +/** + * Claim a reference on this radio nodelist + * + * \param nodelist The radio nodelist object + */ +void dom_html_radio_nodelist_ref(dom_html_radio_nodelist *nodelist) +{ + if (nodelist == NULL) + return; + + nodelist->refcnt ++; +} + +/** + * Relese a reference on this radio nodelist + * + * \param nodelist The radio nodelist object + */ +void dom_html_radio_nodelist_unref(dom_html_radio_nodelist *nodelist) +{ + if (nodelist == NULL) + return; + + if (nodelist->refcnt > 0) + nodelist->refcnt --; + + if (nodelist->refcnt == 0) + _dom_html_radio_nodelist_destroy(nodelist); +} + + +/** + * Retrieve the length of a node list + * + * \param nodelist List to retrieve length of + * \param len Pointer to location to receive length + * \return DOM_NO_ERR. + */ +dom_exception dom_html_radio_nodelist_get_length(dom_html_radio_nodelist *nodelist, + uint32_t *len) +{ + return dom_html_collection_get_length(nodelist->col, len); +} + +/** + * Retrieve an item from a node list + * + * \param nodelist The list to retrieve the item from + * \param index The list index to retrieve + * \param node Pointer to location to receive item + * \return DOM_NO_ERR. + * + * ::index is a zero-based index into ::list. + * ::index lies in the range [0, length-1] + * + * The returned node will have had its reference count increased. The client + * should unref the node once it has finished with it. + */ +dom_exception dom_html_radio_nodelist_item(dom_html_radio_nodelist *nodelist, + uint32_t index, struct dom_node **node) +{ + return dom_html_collection_item(nodelist->col, index, node); +} + +/** + * Retrieve the value of the radio nodelist per 2.7.2.2. + * + * \param nodelist The list to retrieve the value from + * \param value The value string to fill out + * \return DOM_NO_ERR + */ +dom_exception dom_html_radio_nodelist_get_value(dom_html_radio_nodelist *nodelist, + dom_string **value) +{ + dom_exception exc; + dom_node *node = NULL; + uint32_t count; + struct dom_html_document *doc = + (struct dom_html_document *)(((struct dom_node_internal *)nodelist->form)->owner); + + assert(doc != NULL); + + exc = dom_html_collection_get_length(nodelist->col, &count); + + if (exc != DOM_NO_ERR) + return exc; + + /* 1. Let element be the first element in tree order represented by the + * RadioNodeList object that is an input element whose type attribute + * is in the Radio Button state and whose checkedness is + * true. Otherwise, let it be NULL. + */ + + for (uint32_t idx = 0; idx < count; idx++) { + exc = dom_html_collection_item(nodelist->col, idx, &node); + if (exc != DOM_NO_ERR) + return exc; + + assert(node->type == DOM_ELEMENT_NODE); + + if (dom_string_caseless_isequal(((struct dom_node_internal *)node)->name, + doc->elements[DOM_HTML_ELEMENT_TYPE_INPUT])) { + bool checked = false; + dom_string *type; + + exc = dom_element_get_attribute(node, doc->memoised[hds_type], &type); + if (exc != DOM_NO_ERR) { + dom_node_unref(node); + return exc; + } + + if (dom_string_caseless_isequal(type, doc->memoised[hds_radio]) == false) { + dom_node_unref(node); + dom_string_unref(type); + continue; + } + + dom_string_unref(type); + + exc = dom_html_input_element_get_checked((dom_html_input_element *)node, &checked); + + if (exc != DOM_NO_ERR) { + dom_node_unref(node); + return exc; + } + if (checked == true) + break; + } + + dom_node_unref(node); + node = NULL; + } + + /* 2. If element is null, return the empty string. */ + if (node == NULL) { + *value = doc->base._memo_empty; + dom_string_ref(*value); + return DOM_NO_ERR; + } + + exc = dom_html_input_element_get_value((dom_html_input_element *)node, value); + + /* 3. If element is an element with no value attribute, return the + * string "on". + */ + if (*value == NULL) { + *value = doc->memoised[hds_on]; + dom_string_ref(*value); + return DOM_NO_ERR; + } + + /* 4. Otherwise, return the value of element’s value attribute. */ + + dom_node_unref(node); + + return DOM_NO_ERR; +} + +dom_exception dom_html_radio_nodelist_set_value(dom_html_radio_nodelist *nodelist, + dom_string *value) +{ + dom_exception exc; + dom_node *node = NULL; + uint32_t count; + bool goal_is_on = false; + struct dom_html_document *doc = + (struct dom_html_document *)(((struct dom_node_internal *)nodelist->form)->owner); + + assert(doc != NULL); + + exc = dom_html_collection_get_length(nodelist->col, &count); + + if (exc != DOM_NO_ERR) + return exc; + + goal_is_on = dom_string_caseless_isequal(value, doc->memoised[hds_on]); + + /* 1. If the new value is the string "on": let element be the first + * element in tree order represented by the RadioNodeList object that + * is an input element whose type attribute is in the Radio Button + * state and whose value content attribute is either absent, or present + * and equal to the new value, if any. If no such element exists, then + * instead let element be null. + * + * Otherwise: let element be the first element in tree order + * represented by the RadioNodeList object that is an input element + * whose type attribute is in the Radio Button state and whose value + * content attribute is present and equal to the new value, if any. If + * no such element exists, then instead let element be null. + */ + + for (uint32_t idx = 0; idx < count; idx++) { + exc = dom_html_collection_item(nodelist->col, idx, &node); + if (exc != DOM_NO_ERR) + return exc; + + assert(node->type == DOM_ELEMENT_NODE); + if (dom_string_caseless_isequal(((struct dom_node_internal *)node)->name, + doc->elements[DOM_HTML_ELEMENT_TYPE_INPUT])) { + dom_string *type; + dom_string *test_value; + + exc = dom_element_get_attribute(node, doc->memoised[hds_type], &type); + if (exc != DOM_NO_ERR) { + dom_node_unref(node); + return exc; + } + + if (dom_string_caseless_isequal(type, doc->memoised[hds_radio]) == false) { + dom_node_unref(node); + dom_string_unref(type); + continue; + } + + dom_string_unref(type); + + exc = dom_html_input_element_get_value((dom_html_input_element *)node, &test_value); + + if (test_value == NULL) { + if (goal_is_on) { + dom_string_unref(test_value); + break; + } + } else { + if (dom_string_caseless_isequal(test_value, value)) { + dom_string_unref(test_value); + break; + } + } + + dom_string_unref(test_value); + } + + dom_node_unref(node); + node = NULL; + } + + exc = DOM_NO_ERR; + + /* 2. If element is not null, then set its checkedness to true. */ + if (node != NULL) { + exc = dom_html_input_element_set_checked((dom_html_input_element *)node, true); + } + + dom_node_unref(node); + + return exc; +} -- cgit v1.2.3