2008-09-23 06:19:50 +04:00
|
|
|
/*
|
|
|
|
* Copyright 2008 Andrew Sidwell <takkaria@netsurf-browser.org>
|
|
|
|
* Copyright 2008 John-Mark Bell <jmb@netsurf-browser.org>
|
|
|
|
*
|
|
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
|
|
*
|
|
|
|
* NetSurf is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; version 2 of the License.
|
|
|
|
*
|
|
|
|
* NetSurf is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <libxml/HTMLparser.h>
|
|
|
|
#include <libxml/HTMLtree.h>
|
|
|
|
|
|
|
|
#include <hubbub/parser.h>
|
|
|
|
#include <hubbub/tree.h>
|
|
|
|
|
2009-02-20 14:39:25 +03:00
|
|
|
#include "render/form.h"
|
2008-09-23 06:19:50 +04:00
|
|
|
#include "render/parser_binding.h"
|
|
|
|
|
2008-10-14 18:54:49 +04:00
|
|
|
#include "utils/config.h"
|
2008-09-23 06:19:50 +04:00
|
|
|
#include "utils/log.h"
|
|
|
|
#include "utils/talloc.h"
|
2011-10-08 04:21:59 +04:00
|
|
|
#include "utils/utils.h"
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
/**
|
|
|
|
* Private data attached to each DOM node
|
|
|
|
*/
|
|
|
|
typedef struct hubbub_private {
|
|
|
|
binding_private base;
|
|
|
|
|
|
|
|
uint32_t refcnt;
|
|
|
|
} hubbub_private;
|
|
|
|
|
2008-09-23 06:19:50 +04:00
|
|
|
typedef struct hubbub_ctx {
|
|
|
|
hubbub_parser *parser;
|
|
|
|
|
|
|
|
htmlDocPtr document;
|
|
|
|
bool owns_doc;
|
|
|
|
|
2009-07-24 03:05:34 +04:00
|
|
|
binding_quirks_mode quirks;
|
|
|
|
|
2008-09-23 06:19:50 +04:00
|
|
|
const char *encoding;
|
|
|
|
binding_encoding_source encoding_source;
|
|
|
|
|
|
|
|
#define NUM_NAMESPACES (6)
|
|
|
|
xmlNsPtr namespaces[NUM_NAMESPACES];
|
|
|
|
#undef NUM_NAMESPACES
|
|
|
|
|
|
|
|
hubbub_tree_handler tree_handler;
|
2009-02-20 14:39:25 +03:00
|
|
|
|
|
|
|
struct form *forms;
|
2008-09-23 06:19:50 +04:00
|
|
|
} hubbub_ctx;
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
const char *prefix;
|
|
|
|
const char *url;
|
|
|
|
} namespaces[] = {
|
|
|
|
{ NULL, NULL },
|
|
|
|
{ NULL, "http://www.w3.org/1999/xhtml" },
|
|
|
|
{ "math", "http://www.w3.org/1998/Math/MathML" },
|
|
|
|
{ "svg", "http://www.w3.org/2000/svg" },
|
|
|
|
{ "xlink", "http://www.w3.org/1999/xlink" },
|
|
|
|
/** \todo Oh dear. LibXML2 refuses to create any namespace with a
|
|
|
|
* prefix of "xml". That sucks, royally. */
|
|
|
|
{ "xml", "http://www.w3.org/XML/1998/namespace" },
|
|
|
|
{ "xmlns", "http://www.w3.org/2000/xmlns/" }
|
|
|
|
};
|
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
static hubbub_private *create_private(uint32_t refcnt);
|
2011-10-08 04:21:59 +04:00
|
|
|
static hubbub_private *copy_private(const hubbub_private *p, uint32_t refcnt);
|
|
|
|
static void destroy_private(hubbub_private *p);
|
2008-09-23 06:19:50 +04:00
|
|
|
static inline char *c_string_from_hubbub_string(hubbub_ctx *ctx,
|
|
|
|
const hubbub_string *str);
|
|
|
|
static void create_namespaces(hubbub_ctx *ctx, xmlNode *root);
|
2009-04-15 16:26:25 +04:00
|
|
|
static hubbub_error create_comment(void *ctx, const hubbub_string *data,
|
2008-09-23 06:19:50 +04:00
|
|
|
void **result);
|
2009-04-15 16:26:25 +04:00
|
|
|
static hubbub_error create_doctype(void *ctx, const hubbub_doctype *doctype,
|
2008-09-23 06:19:50 +04:00
|
|
|
void **result);
|
2009-04-15 16:26:25 +04:00
|
|
|
static hubbub_error create_element(void *ctx, const hubbub_tag *tag,
|
|
|
|
void **result);
|
|
|
|
static hubbub_error create_text(void *ctx, const hubbub_string *data,
|
|
|
|
void **result);
|
|
|
|
static hubbub_error ref_node(void *ctx, void *node);
|
|
|
|
static hubbub_error unref_node(void *ctx, void *node);
|
|
|
|
static hubbub_error append_child(void *ctx, void *parent, void *child,
|
|
|
|
void **result);
|
|
|
|
static hubbub_error insert_before(void *ctx, void *parent, void *child,
|
|
|
|
void *ref_child, void **result);
|
|
|
|
static hubbub_error remove_child(void *ctx, void *parent, void *child,
|
|
|
|
void **result);
|
|
|
|
static hubbub_error clone_node(void *ctx, void *node, bool deep, void **result);
|
|
|
|
static hubbub_error reparent_children(void *ctx, void *node, void *new_parent);
|
|
|
|
static hubbub_error get_parent(void *ctx, void *node, bool element_only,
|
|
|
|
void **result);
|
|
|
|
static hubbub_error has_children(void *ctx, void *node, bool *result);
|
|
|
|
static hubbub_error form_associate(void *ctx, void *form, void *node);
|
|
|
|
static hubbub_error add_attributes(void *ctx, void *node,
|
2008-09-23 06:19:50 +04:00
|
|
|
const hubbub_attribute *attributes, uint32_t n_attributes);
|
2009-04-15 16:26:25 +04:00
|
|
|
static hubbub_error set_quirks_mode(void *ctx, hubbub_quirks_mode mode);
|
|
|
|
static hubbub_error change_encoding(void *ctx, const char *charset);
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2009-02-20 14:39:25 +03:00
|
|
|
static struct form *parse_form_element(xmlNode *node, const char *docenc);
|
|
|
|
static struct form_control *parse_input_element(xmlNode *node);
|
|
|
|
static struct form_control *parse_button_element(xmlNode *node);
|
|
|
|
static struct form_control *parse_select_element(xmlNode *node);
|
|
|
|
static struct form_control *parse_textarea_element(xmlNode *node);
|
|
|
|
|
2008-09-23 06:19:50 +04:00
|
|
|
static hubbub_tree_handler tree_handler = {
|
|
|
|
create_comment,
|
|
|
|
create_doctype,
|
|
|
|
create_element,
|
|
|
|
create_text,
|
|
|
|
ref_node,
|
|
|
|
unref_node,
|
|
|
|
append_child,
|
|
|
|
insert_before,
|
|
|
|
remove_child,
|
|
|
|
clone_node,
|
|
|
|
reparent_children,
|
|
|
|
get_parent,
|
|
|
|
has_children,
|
|
|
|
form_associate,
|
|
|
|
add_attributes,
|
|
|
|
set_quirks_mode,
|
|
|
|
change_encoding,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2010-04-30 20:06:03 +04:00
|
|
|
static void *ns_talloc_based_realloc(void *ptr, size_t len, void *pw)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
2010-04-30 11:00:58 +04:00
|
|
|
/* talloc_realloc_size(pw, ptr, 0) == talloc_free(ptr) */
|
2008-09-23 06:19:50 +04:00
|
|
|
return talloc_realloc_size(pw, ptr, len);
|
|
|
|
}
|
|
|
|
|
2008-11-09 22:04:30 +03:00
|
|
|
binding_error binding_create_tree(void *arena, const char *charset, void **ctx)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
2008-11-09 22:04:30 +03:00
|
|
|
hubbub_ctx *c;
|
2008-09-23 06:19:50 +04:00
|
|
|
hubbub_parser_optparams params;
|
2008-11-30 01:53:58 +03:00
|
|
|
uint32_t i;
|
2008-11-09 22:04:30 +03:00
|
|
|
hubbub_error error;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2008-11-09 22:04:30 +03:00
|
|
|
c = malloc(sizeof(hubbub_ctx));
|
|
|
|
if (c == NULL)
|
|
|
|
return BINDING_NOMEM;
|
|
|
|
|
|
|
|
c->parser = NULL;
|
|
|
|
c->encoding = charset;
|
2009-02-20 15:50:34 +03:00
|
|
|
c->encoding_source = charset != NULL ? ENCODING_SOURCE_HEADER
|
|
|
|
: ENCODING_SOURCE_DETECTED;
|
2008-11-09 22:04:30 +03:00
|
|
|
c->document = NULL;
|
|
|
|
c->owns_doc = true;
|
2009-07-24 03:05:34 +04:00
|
|
|
c->quirks = BINDING_QUIRKS_MODE_NONE;
|
2009-02-20 14:39:25 +03:00
|
|
|
c->forms = NULL;
|
2008-11-09 22:04:30 +03:00
|
|
|
|
2010-04-30 20:06:03 +04:00
|
|
|
error = hubbub_parser_create(charset, true, ns_talloc_based_realloc,
|
|
|
|
arena, &c->parser);
|
2008-11-09 22:04:30 +03:00
|
|
|
if (error != HUBBUB_OK) {
|
|
|
|
free(c);
|
|
|
|
if (error == HUBBUB_BADENCODING)
|
|
|
|
return BINDING_BADENCODING;
|
|
|
|
else
|
|
|
|
return BINDING_NOMEM; /* Assume OOM */
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2008-11-09 22:04:30 +03:00
|
|
|
c->document = htmlNewDocNoDtD(NULL, NULL);
|
|
|
|
if (c->document == NULL) {
|
|
|
|
hubbub_parser_destroy(c->parser);
|
|
|
|
free(c);
|
|
|
|
return BINDING_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
2011-09-29 23:15:54 +04:00
|
|
|
c->document->_private = create_private(0);
|
|
|
|
if (c->document->_private == NULL) {
|
|
|
|
xmlFreeDoc(c->document);
|
|
|
|
hubbub_parser_destroy(c->parser);
|
|
|
|
free(c);
|
|
|
|
return BINDING_NOMEM;
|
|
|
|
}
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2009-02-20 14:39:25 +03:00
|
|
|
for (i = 0; i < sizeof(c->namespaces) / sizeof(c->namespaces[0]); i++) {
|
2008-11-09 22:04:30 +03:00
|
|
|
c->namespaces[i] = NULL;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2008-11-09 22:04:30 +03:00
|
|
|
c->tree_handler = tree_handler;
|
|
|
|
c->tree_handler.ctx = (void *) c;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2008-11-09 22:04:30 +03:00
|
|
|
params.tree_handler = &c->tree_handler;
|
|
|
|
hubbub_parser_setopt(c->parser, HUBBUB_PARSER_TREE_HANDLER, ¶ms);
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2008-11-09 22:04:30 +03:00
|
|
|
ref_node(c, c->document);
|
|
|
|
params.document_node = c->document;
|
|
|
|
hubbub_parser_setopt(c->parser, HUBBUB_PARSER_DOCUMENT_NODE, ¶ms);
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2008-11-09 22:04:30 +03:00
|
|
|
*ctx = (void *) c;
|
|
|
|
|
|
|
|
return BINDING_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2008-11-09 22:04:30 +03:00
|
|
|
binding_error binding_destroy_tree(void *ctx)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
|
|
|
|
if (ctx == NULL)
|
2008-11-09 22:04:30 +03:00
|
|
|
return BINDING_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
if (c->parser != NULL)
|
|
|
|
hubbub_parser_destroy(c->parser);
|
|
|
|
|
|
|
|
if (c->owns_doc)
|
2011-09-29 23:15:54 +04:00
|
|
|
binding_destroy_document(c->document);
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
c->parser = NULL;
|
|
|
|
c->encoding = NULL;
|
|
|
|
c->document = NULL;
|
|
|
|
|
|
|
|
free(c);
|
2008-11-09 22:04:30 +03:00
|
|
|
|
|
|
|
return BINDING_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
binding_error binding_parse_chunk(void *ctx, const uint8_t *data, size_t len)
|
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
hubbub_error err;
|
|
|
|
|
|
|
|
err = hubbub_parser_parse_chunk(c->parser, (uint8_t *) data, len);
|
|
|
|
if (err == HUBBUB_ENCODINGCHANGE)
|
|
|
|
return BINDING_ENCODINGCHANGE;
|
|
|
|
|
2009-04-15 15:28:07 +04:00
|
|
|
return err == HUBBUB_NOMEM ? BINDING_NOMEM : BINDING_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
binding_error binding_parse_completed(void *ctx)
|
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
hubbub_error error;
|
|
|
|
|
|
|
|
error = hubbub_parser_completed(c->parser);
|
|
|
|
|
2009-04-15 15:28:07 +04:00
|
|
|
return error == HUBBUB_NOMEM ? BINDING_NOMEM : BINDING_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *binding_get_encoding(void *ctx, binding_encoding_source *source)
|
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
|
|
|
|
*source = c->encoding_source;
|
|
|
|
|
2009-02-20 15:50:34 +03:00
|
|
|
return c->encoding != NULL ? c->encoding : "Windows-1252";
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-07-24 03:05:34 +04:00
|
|
|
xmlDocPtr binding_get_document(void *ctx, binding_quirks_mode *quirks)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
xmlDocPtr doc = c->document;
|
|
|
|
|
|
|
|
c->owns_doc = false;
|
|
|
|
|
2009-07-24 03:05:34 +04:00
|
|
|
*quirks = c->quirks;
|
|
|
|
|
2008-09-23 06:19:50 +04:00
|
|
|
return doc;
|
|
|
|
}
|
|
|
|
|
2009-02-20 14:39:25 +03:00
|
|
|
struct form *binding_get_forms(void *ctx)
|
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
|
|
|
|
return c->forms;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct form_control *binding_get_control_for_node(void *ctx, xmlNodePtr node)
|
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
struct form *f;
|
|
|
|
struct form_control *ctl = NULL;
|
|
|
|
|
|
|
|
for (f = c->forms; f != NULL; f = f->prev) {
|
|
|
|
for (ctl = f->controls; ctl != NULL; ctl = ctl->next) {
|
|
|
|
if (ctl->node == node)
|
|
|
|
return ctl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No control found. This implies that it's not associated
|
|
|
|
* with any form. In this case, we create a control for it
|
|
|
|
* on the fly. */
|
|
|
|
if (strcasecmp((const char *) node->name, "input") == 0) {
|
|
|
|
ctl = parse_input_element(node);
|
|
|
|
} else if (strcasecmp((const char *) node->name, "button") == 0) {
|
|
|
|
ctl = parse_button_element(node);
|
|
|
|
} else if (strcasecmp((const char *) node->name, "select") == 0) {
|
|
|
|
ctl = parse_select_element(node);
|
|
|
|
} else if (strcasecmp((const char *) node->name, "textarea") == 0) {
|
|
|
|
ctl = parse_textarea_element(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctl;
|
|
|
|
}
|
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
void binding_destroy_document(xmlDocPtr doc)
|
|
|
|
{
|
|
|
|
xmlNode *n = (xmlNode *) doc;
|
|
|
|
|
|
|
|
while (n != NULL) {
|
2011-10-08 04:21:59 +04:00
|
|
|
destroy_private(n->_private);
|
2011-09-29 23:15:54 +04:00
|
|
|
|
|
|
|
if (n->children != NULL) {
|
|
|
|
n = n->children;
|
|
|
|
} else if (n->next != NULL) {
|
|
|
|
n = n->next;
|
|
|
|
} else {
|
|
|
|
while (n->parent != NULL && n->parent->next == NULL)
|
|
|
|
n = n->parent;
|
|
|
|
|
|
|
|
if (n->parent != NULL)
|
|
|
|
n = n->parent->next;
|
|
|
|
else
|
|
|
|
n = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xmlFreeDoc(doc);
|
|
|
|
}
|
|
|
|
|
2008-09-23 06:19:50 +04:00
|
|
|
/*****************************************************************************/
|
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
hubbub_private *create_private(uint32_t refcnt)
|
|
|
|
{
|
|
|
|
hubbub_private *pvt = calloc(1, sizeof(*pvt));
|
|
|
|
|
|
|
|
if (pvt != NULL)
|
|
|
|
pvt->refcnt = refcnt;
|
|
|
|
|
|
|
|
return pvt;
|
|
|
|
}
|
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
hubbub_private *copy_private(const hubbub_private *p, uint32_t refcnt)
|
|
|
|
{
|
|
|
|
hubbub_private *pvt = calloc(1, sizeof(*pvt));
|
|
|
|
|
|
|
|
if (pvt != NULL) {
|
|
|
|
pvt->refcnt = refcnt;
|
|
|
|
|
|
|
|
if (p->base.nclasses > 0) {
|
|
|
|
pvt->base.classes =
|
|
|
|
malloc(p->base.nclasses * sizeof(lwc_string *));
|
|
|
|
if (pvt->base.classes == NULL) {
|
|
|
|
free(pvt);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (pvt->base.nclasses < p->base.nclasses) {
|
|
|
|
pvt->base.classes[pvt->base.nclasses] =
|
|
|
|
lwc_string_ref(p->base.classes[
|
|
|
|
pvt->base.nclasses]);
|
2011-10-19 19:28:47 +04:00
|
|
|
pvt->base.nclasses++;
|
2011-10-08 04:21:59 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p->base.localname != NULL)
|
|
|
|
pvt->base.localname = lwc_string_ref(p->base.localname);
|
|
|
|
|
|
|
|
if (p->base.id != NULL)
|
|
|
|
pvt->base.id = lwc_string_ref(p->base.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pvt;
|
|
|
|
}
|
|
|
|
|
|
|
|
void destroy_private(hubbub_private *p)
|
|
|
|
{
|
|
|
|
if (p->base.localname != NULL)
|
|
|
|
lwc_string_unref(p->base.localname);
|
|
|
|
|
|
|
|
if (p->base.id != NULL)
|
|
|
|
lwc_string_unref(p->base.id);
|
|
|
|
|
|
|
|
while (p->base.nclasses > 0)
|
|
|
|
lwc_string_unref(p->base.classes[--p->base.nclasses]);
|
|
|
|
|
|
|
|
if (p->base.classes != NULL)
|
|
|
|
free(p->base.classes);
|
|
|
|
|
|
|
|
free(p);
|
|
|
|
}
|
|
|
|
|
2008-09-23 06:19:50 +04:00
|
|
|
char *c_string_from_hubbub_string(hubbub_ctx *ctx, const hubbub_string *str)
|
|
|
|
{
|
|
|
|
return strndup((const char *) str->ptr, (int) str->len);
|
|
|
|
}
|
|
|
|
|
|
|
|
void create_namespaces(hubbub_ctx *ctx, xmlNode *root)
|
|
|
|
{
|
2008-11-30 01:53:58 +03:00
|
|
|
uint32_t i;
|
2009-02-20 14:39:25 +03:00
|
|
|
|
|
|
|
for (i = 1; i < sizeof(namespaces) / sizeof(namespaces[0]); i++) {
|
2008-09-23 06:19:50 +04:00
|
|
|
ctx->namespaces[i - 1] = xmlNewNs(root,
|
|
|
|
BAD_CAST namespaces[i].url,
|
|
|
|
BAD_CAST namespaces[i].prefix);
|
|
|
|
|
|
|
|
if (ctx->namespaces[i - 1] == NULL) {
|
|
|
|
LOG(("Failed creating namespace %s\n",
|
|
|
|
namespaces[i].prefix));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error create_comment(void *ctx, const hubbub_string *data, void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
char *content;
|
|
|
|
xmlNodePtr n;
|
|
|
|
|
|
|
|
content = c_string_from_hubbub_string(c, data);
|
|
|
|
if (content == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
n = xmlNewDocComment(c->document, BAD_CAST content);
|
|
|
|
if (n == NULL) {
|
|
|
|
free(content);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
2011-09-29 23:15:54 +04:00
|
|
|
n->_private = create_private(1);
|
|
|
|
if (n->_private == NULL) {
|
|
|
|
xmlFreeNode(n);
|
|
|
|
free(content);
|
|
|
|
return HUBBUB_NOMEM;
|
|
|
|
}
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
free(content);
|
|
|
|
|
|
|
|
*result = (void *) n;
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error create_doctype(void *ctx, const hubbub_doctype *doctype,
|
|
|
|
void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
char *name, *public = NULL, *system = NULL;
|
|
|
|
xmlDtdPtr n;
|
|
|
|
|
|
|
|
name = c_string_from_hubbub_string(c, &doctype->name);
|
|
|
|
if (name == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
if (!doctype->public_missing) {
|
|
|
|
public = c_string_from_hubbub_string(c, &doctype->public_id);
|
|
|
|
if (public == NULL) {
|
|
|
|
free(name);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!doctype->system_missing) {
|
|
|
|
system = c_string_from_hubbub_string(c, &doctype->system_id);
|
|
|
|
if (system == NULL) {
|
|
|
|
free(public);
|
|
|
|
free(name);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
n = xmlNewDtd(c->document, BAD_CAST name,
|
|
|
|
BAD_CAST (public ? public : ""),
|
|
|
|
BAD_CAST (system ? system : ""));
|
|
|
|
if (n == NULL) {
|
|
|
|
free(system);
|
|
|
|
free(public);
|
|
|
|
free(name);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
2011-09-29 23:15:54 +04:00
|
|
|
n->_private = create_private(1);
|
|
|
|
if (n->_private == NULL) {
|
|
|
|
xmlFreeDtd(n);
|
|
|
|
free(system);
|
|
|
|
free(public);
|
|
|
|
free(name);
|
|
|
|
return HUBBUB_NOMEM;
|
|
|
|
}
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
*result = (void *) n;
|
|
|
|
|
|
|
|
free(system);
|
|
|
|
free(public);
|
|
|
|
free(name);
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error create_element(void *ctx, const hubbub_tag *tag, void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
2011-10-08 04:21:59 +04:00
|
|
|
lwc_string *iname;
|
2008-09-23 06:19:50 +04:00
|
|
|
xmlNodePtr n;
|
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
if (lwc_intern_string((const char *) tag->name.ptr, tag->name.len,
|
|
|
|
&iname) != lwc_error_ok) {
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2011-10-08 04:21:59 +04:00
|
|
|
}
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
if (c->namespaces[0] != NULL) {
|
|
|
|
n = xmlNewDocNode(c->document, c->namespaces[tag->ns - 1],
|
2011-10-08 04:21:59 +04:00
|
|
|
BAD_CAST lwc_string_data(iname), NULL);
|
2008-09-23 06:19:50 +04:00
|
|
|
} else {
|
2011-10-08 04:21:59 +04:00
|
|
|
n = xmlNewDocNode(c->document, NULL,
|
|
|
|
BAD_CAST lwc_string_data(iname), NULL);
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
/* We're creating the root node of the document. Therefore,
|
|
|
|
* create the namespaces and set this node's namespace */
|
|
|
|
if (n != NULL && c->namespaces[0] == NULL) {
|
|
|
|
create_namespaces(c, (void *) n);
|
|
|
|
|
|
|
|
xmlSetNs(n, c->namespaces[tag->ns - 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (n == NULL) {
|
2011-10-08 04:21:59 +04:00
|
|
|
lwc_string_unref(iname);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
2011-09-29 23:15:54 +04:00
|
|
|
n->_private = create_private(1);
|
|
|
|
if (n->_private == NULL) {
|
|
|
|
xmlFreeNode(n);
|
2011-10-08 04:21:59 +04:00
|
|
|
lwc_string_unref(iname);
|
2011-09-29 23:15:54 +04:00
|
|
|
return HUBBUB_NOMEM;
|
|
|
|
}
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
if (tag->n_attributes > 0 && add_attributes(ctx, (void *) n,
|
2009-04-15 16:26:25 +04:00
|
|
|
tag->attributes, tag->n_attributes) != HUBBUB_OK) {
|
2011-10-08 04:21:59 +04:00
|
|
|
destroy_private(n->_private);
|
2008-09-23 06:19:50 +04:00
|
|
|
xmlFreeNode(n);
|
2011-10-08 04:21:59 +04:00
|
|
|
lwc_string_unref(iname);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
if (lwc_string_length(iname) == SLEN("form") &&
|
|
|
|
strcasecmp(lwc_string_data(iname), "form") == 0) {
|
2009-02-20 14:39:25 +03:00
|
|
|
struct form *form = parse_form_element(n, c->encoding);
|
|
|
|
|
|
|
|
/* Memory exhaustion */
|
|
|
|
if (form == NULL) {
|
2011-10-08 04:21:59 +04:00
|
|
|
destroy_private(n->_private);
|
2009-02-20 14:39:25 +03:00
|
|
|
xmlFreeNode(n);
|
2011-10-08 04:21:59 +04:00
|
|
|
lwc_string_unref(iname);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2009-02-20 14:39:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Insert into list */
|
|
|
|
form->prev = c->forms;
|
|
|
|
c->forms = form;
|
|
|
|
}
|
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
((binding_private *) n->_private)->localname = iname;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
*result = (void *) n;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error create_text(void *ctx, const hubbub_string *data, void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
xmlNodePtr n;
|
|
|
|
|
|
|
|
n = xmlNewDocTextLen(c->document, BAD_CAST data->ptr, (int) data->len);
|
|
|
|
if (n == NULL) {
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
2011-09-29 23:15:54 +04:00
|
|
|
n->_private = create_private(1);
|
|
|
|
if (n->_private == NULL) {
|
|
|
|
xmlFreeNode(n);
|
|
|
|
return HUBBUB_NOMEM;
|
|
|
|
}
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
*result = (void *) n;
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error ref_node(void *ctx, void *node)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
2011-09-29 23:15:54 +04:00
|
|
|
hubbub_private *pvt;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
if (node == c->document) {
|
|
|
|
xmlDoc *n = (xmlDoc *) node;
|
2011-09-29 23:15:54 +04:00
|
|
|
pvt = n->_private;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
pvt->refcnt++;
|
2008-09-23 06:19:50 +04:00
|
|
|
} else {
|
|
|
|
xmlNode *n = (xmlNode *) node;
|
2011-09-29 23:15:54 +04:00
|
|
|
pvt = n->_private;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
pvt->refcnt++;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error unref_node(void *ctx, void *node)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
2011-09-29 23:15:54 +04:00
|
|
|
hubbub_private *pvt;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
if (node == c->document) {
|
|
|
|
xmlDoc *n = (xmlDoc *) node;
|
2011-09-29 23:15:54 +04:00
|
|
|
pvt = n->_private;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
assert(pvt->refcnt != 0 && "Node has refcount of zero");
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
pvt->refcnt--;
|
2008-09-23 06:19:50 +04:00
|
|
|
} else {
|
|
|
|
xmlNode *n = (xmlNode *) node;
|
2011-09-29 23:15:54 +04:00
|
|
|
pvt = n->_private;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
assert(pvt->refcnt != 0 && "Node has refcount of zero");
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
pvt->refcnt--;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-09-29 23:15:54 +04:00
|
|
|
if (pvt->refcnt == 0 && n->parent == NULL) {
|
2011-10-08 04:21:59 +04:00
|
|
|
destroy_private(pvt);
|
2008-09-23 06:19:50 +04:00
|
|
|
xmlFreeNode(n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error append_child(void *ctx, void *parent, void *child, void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
xmlNode *chld = (xmlNode *) child;
|
|
|
|
xmlNode *p = (xmlNode *) parent;
|
|
|
|
|
2009-03-11 02:13:08 +03:00
|
|
|
/** \todo Text node merging logic as per
|
|
|
|
* http://www.whatwg.org/specs/web-apps/current-work/multipage/ \
|
|
|
|
* tree-construction.html#insert-a-character
|
|
|
|
*
|
|
|
|
* Doesn't actually matter for us until we have scripting. Thus,
|
|
|
|
* this is something which can wait until libdom.
|
|
|
|
*/
|
2008-09-23 06:19:50 +04:00
|
|
|
if (chld->type == XML_TEXT_NODE && p->last != NULL &&
|
|
|
|
p->last->type == XML_TEXT_NODE) {
|
|
|
|
/* Need to clone the child, as libxml will free it if it
|
|
|
|
* merges the content with a pre-existing text node. */
|
|
|
|
chld = xmlCopyNode(chld, 0);
|
|
|
|
if (chld == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
*result = xmlAddChild(p, chld);
|
|
|
|
|
|
|
|
assert(*result != (void *) chld);
|
|
|
|
} else {
|
|
|
|
*result = xmlAddChild(p, chld);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*result == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
ref_node(ctx, *result);
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error insert_before(void *ctx, void *parent, void *child,
|
|
|
|
void *ref_child, void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
xmlNode *chld = (xmlNode *) child;
|
|
|
|
xmlNode *ref = (xmlNode *) ref_child;
|
|
|
|
|
|
|
|
if (chld->type == XML_TEXT_NODE && ref->prev != NULL &&
|
|
|
|
ref->prev->type == XML_TEXT_NODE) {
|
|
|
|
/* Clone text node, as it'll be freed by libxml */
|
|
|
|
chld = xmlCopyNode(chld, 0);
|
|
|
|
if (chld == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
*result = xmlAddNextSibling(ref->prev, chld);
|
|
|
|
|
|
|
|
assert(*result != (void *) chld);
|
|
|
|
} else {
|
|
|
|
*result = xmlAddPrevSibling(ref, chld);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*result == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
ref_node(ctx, *result);
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error remove_child(void *ctx, void *parent, void *child, void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
xmlNode *chld = (xmlNode *) child;
|
|
|
|
|
|
|
|
xmlUnlinkNode(chld);
|
|
|
|
|
|
|
|
*result = child;
|
|
|
|
|
|
|
|
ref_node(ctx, *result);
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error clone_node(void *ctx, void *node, bool deep, void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
xmlNode *n = (xmlNode *) node;
|
2011-10-08 04:21:59 +04:00
|
|
|
xmlNode *clonedtree;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
/* Shallow clone node */
|
|
|
|
clonedtree = xmlCopyNode(n, 2);
|
|
|
|
if (clonedtree == NULL)
|
2011-09-29 23:15:54 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
clonedtree->_private = copy_private(n->_private, 1);
|
|
|
|
if (clonedtree->_private == NULL) {
|
|
|
|
xmlFreeNode(clonedtree);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2011-09-29 23:15:54 +04:00
|
|
|
}
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
/* Iteratively clone children too, if required */
|
|
|
|
if (deep && n->children != NULL) {
|
|
|
|
xmlNode *parent = clonedtree, *copy;
|
|
|
|
|
|
|
|
n = n->children;
|
|
|
|
|
|
|
|
while (n != node) {
|
|
|
|
copy = xmlCopyNode(n, 2);
|
|
|
|
if (copy == NULL)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
copy->_private = copy_private(n->_private, 0);
|
|
|
|
if (copy->_private == NULL) {
|
|
|
|
xmlFreeNode(copy);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
xmlAddChild(parent, copy);
|
|
|
|
|
|
|
|
if (n->children != NULL) {
|
|
|
|
parent = copy;
|
|
|
|
n = n->children;
|
|
|
|
} else if (n->next != NULL) {
|
|
|
|
n = n->next;
|
|
|
|
} else {
|
|
|
|
while (n->parent != node &&
|
|
|
|
n->parent->next == NULL) {
|
|
|
|
parent = parent->parent;
|
|
|
|
n = n->parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n->parent != node) {
|
|
|
|
parent = parent->parent;
|
|
|
|
n = n->parent->next;
|
|
|
|
} else
|
|
|
|
n = node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*result = clonedtree;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2011-10-08 04:21:59 +04:00
|
|
|
|
|
|
|
error:
|
|
|
|
n = clonedtree;
|
|
|
|
|
|
|
|
while (n != NULL) {
|
|
|
|
destroy_private(n->_private);
|
|
|
|
|
|
|
|
if (n->children != NULL) {
|
|
|
|
n = n->children;
|
|
|
|
} else if (n->next != NULL) {
|
|
|
|
n = n->next;
|
|
|
|
} else {
|
|
|
|
while (n->parent != NULL && n->parent->next == NULL) {
|
|
|
|
n = n->parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n->parent != NULL)
|
|
|
|
n = n->parent->next;
|
|
|
|
else
|
|
|
|
n = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xmlFreeNode(clonedtree);
|
|
|
|
|
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error reparent_children(void *ctx, void *node, void *new_parent)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
xmlNode *n = (xmlNode *) node;
|
|
|
|
xmlNode *p = (xmlNode *) new_parent;
|
2008-11-30 01:53:58 +03:00
|
|
|
xmlNode *child;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2008-11-30 01:53:58 +03:00
|
|
|
for (child = n->children; child != NULL; ) {
|
2008-09-23 06:19:50 +04:00
|
|
|
xmlNode *next = child->next;
|
|
|
|
|
|
|
|
xmlUnlinkNode(child);
|
|
|
|
|
|
|
|
if (xmlAddChild(p, child) == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
child = next;
|
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error get_parent(void *ctx, void *node, bool element_only, void **result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
xmlNode *n = (xmlNode *) node;
|
|
|
|
|
|
|
|
*result = (void *) n->parent;
|
|
|
|
|
|
|
|
if (*result != NULL && element_only &&
|
|
|
|
((xmlNode *) *result)->type != XML_ELEMENT_NODE) {
|
|
|
|
*result = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*result != NULL)
|
|
|
|
ref_node(ctx, *result);
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error has_children(void *ctx, void *node, bool *result)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
xmlNode *n = (xmlNode *) node;
|
|
|
|
|
|
|
|
*result = n->children != NULL;
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error form_associate(void *ctx, void *form, void *node)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
2009-02-20 14:39:25 +03:00
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
xmlNode *n = (xmlNode *) node;
|
|
|
|
struct form *f;
|
|
|
|
struct form_control *control = NULL;
|
2009-03-11 02:13:08 +03:00
|
|
|
xmlChar *id = NULL;
|
|
|
|
|
|
|
|
/* Find form object to associate with:
|
|
|
|
*
|
|
|
|
* 1) If node possesses an @form, use the form with a matching @id
|
|
|
|
* 2) Otherwise, use the form provided
|
|
|
|
*/
|
|
|
|
id = xmlGetProp(n, (const xmlChar *) "form");
|
2009-02-20 14:39:25 +03:00
|
|
|
for (f = c->forms; f != NULL; f = f->prev) {
|
2009-03-11 02:13:08 +03:00
|
|
|
if (id == NULL && f->node == form) {
|
2009-02-20 14:39:25 +03:00
|
|
|
break;
|
2009-03-11 02:13:08 +03:00
|
|
|
} else if (id != NULL) {
|
|
|
|
xmlNode *fn = (xmlNode *) f->node;
|
|
|
|
xmlChar *fid = xmlGetProp(fn, (const xmlChar *) "id");
|
|
|
|
|
|
|
|
if (fid != NULL && strcmp((char *) id,
|
|
|
|
(char *) fid) == 0) {
|
|
|
|
xmlFree(fid);
|
|
|
|
break;
|
|
|
|
} else if (fid != NULL) {
|
|
|
|
xmlFree(fid);
|
|
|
|
}
|
|
|
|
}
|
2009-02-20 14:39:25 +03:00
|
|
|
}
|
2009-03-11 02:13:08 +03:00
|
|
|
if (id != NULL)
|
|
|
|
xmlFree(id);
|
2009-02-20 14:39:25 +03:00
|
|
|
|
|
|
|
/* None found -- give up */
|
|
|
|
if (f == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2009-02-20 14:39:25 +03:00
|
|
|
|
2009-03-11 02:13:08 +03:00
|
|
|
/* Will be one of: button, fieldset, input, label,
|
|
|
|
* output, select, textarea.
|
|
|
|
*
|
|
|
|
* We ignore fieldset, label and output.
|
|
|
|
*/
|
2009-02-20 14:39:25 +03:00
|
|
|
if (strcasecmp((const char *) n->name, "input") == 0) {
|
|
|
|
control = parse_input_element(n);
|
|
|
|
} else if (strcasecmp((const char *) n->name, "button") == 0) {
|
|
|
|
control = parse_button_element(n);
|
|
|
|
} else if (strcasecmp((const char *) n->name, "select") == 0) {
|
|
|
|
control = parse_select_element(n);
|
|
|
|
} else if (strcasecmp((const char *) n->name, "textarea") == 0) {
|
|
|
|
control = parse_textarea_element(n);
|
|
|
|
} else
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2009-02-20 14:39:25 +03:00
|
|
|
|
|
|
|
/* Memory exhaustion */
|
|
|
|
if (control == NULL)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2009-02-20 14:39:25 +03:00
|
|
|
|
|
|
|
/* Add the control to the form */
|
|
|
|
form_add_control(f, control);
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
static hubbub_error parse_class_attr(lwc_string *value,
|
|
|
|
lwc_string ***classes, uint32_t *nclasses)
|
|
|
|
{
|
|
|
|
const char *pv;
|
|
|
|
lwc_string **cls = NULL;
|
|
|
|
uint32_t count = 0;
|
|
|
|
|
|
|
|
/* Count number of classes */
|
|
|
|
for (pv = lwc_string_data(value); *pv != '\0'; ) {
|
|
|
|
if (*pv != ' ') {
|
|
|
|
while (*pv != ' ' && *pv != '\0')
|
|
|
|
pv++;
|
|
|
|
count++;
|
|
|
|
} else {
|
|
|
|
while (*pv == ' ')
|
|
|
|
pv++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If there are some, unpack them */
|
|
|
|
if (count > 0) {
|
|
|
|
cls = malloc(count * sizeof(lwc_string *));
|
|
|
|
if (cls == NULL)
|
|
|
|
return HUBBUB_NOMEM;
|
|
|
|
|
|
|
|
for (pv = lwc_string_data(value), count = 0; *pv != '\0'; ) {
|
|
|
|
if (*pv != ' ') {
|
|
|
|
const char *s = pv;
|
|
|
|
while (*pv != ' ' && *pv != '\0')
|
|
|
|
pv++;
|
|
|
|
if (lwc_intern_string(s, pv - s,
|
|
|
|
&cls[count]) != lwc_error_ok)
|
|
|
|
goto error;
|
|
|
|
count++;
|
|
|
|
} else {
|
|
|
|
while (*pv == ' ')
|
|
|
|
pv++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*classes = cls;
|
|
|
|
*nclasses = count;
|
|
|
|
|
|
|
|
return HUBBUB_OK;
|
|
|
|
error:
|
|
|
|
while (count > 0)
|
|
|
|
lwc_string_unref(cls[--count]);
|
|
|
|
|
|
|
|
free(cls);
|
|
|
|
|
|
|
|
return HUBBUB_NOMEM;
|
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error add_attributes(void *ctx, void *node,
|
2008-09-23 06:19:50 +04:00
|
|
|
const hubbub_attribute *attributes, uint32_t n_attributes)
|
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
xmlNode *n = (xmlNode *) node;
|
2011-10-08 04:21:59 +04:00
|
|
|
binding_private *p = n->_private;
|
2008-11-30 01:53:58 +03:00
|
|
|
uint32_t attr;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2008-11-30 01:53:58 +03:00
|
|
|
for (attr = 0; attr < n_attributes; attr++) {
|
2008-09-23 06:19:50 +04:00
|
|
|
xmlAttr *prop;
|
2011-10-08 04:21:59 +04:00
|
|
|
lwc_string *name, *value;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
if (lwc_intern_string((const char *) attributes[attr].name.ptr,
|
|
|
|
attributes[attr].name.len,
|
|
|
|
&name) != lwc_error_ok)
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
2011-10-08 04:21:59 +04:00
|
|
|
if (lwc_intern_string((const char *) attributes[attr].value.ptr,
|
|
|
|
attributes[attr].value.len,
|
|
|
|
&value) != lwc_error_ok) {
|
|
|
|
lwc_string_unref(name);
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (attributes[attr].ns != HUBBUB_NS_NULL &&
|
|
|
|
c->namespaces[0] != NULL) {
|
|
|
|
prop = xmlNewNsProp(n,
|
|
|
|
c->namespaces[attributes[attr].ns - 1],
|
2011-10-08 04:21:59 +04:00
|
|
|
BAD_CAST lwc_string_data(name),
|
|
|
|
BAD_CAST lwc_string_data(value));
|
2008-09-23 06:19:50 +04:00
|
|
|
} else {
|
2011-10-08 04:21:59 +04:00
|
|
|
prop = xmlNewProp(n, BAD_CAST lwc_string_data(name),
|
|
|
|
BAD_CAST lwc_string_data(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle @id / @class */
|
|
|
|
if (p->id == NULL && lwc_string_length(name) == SLEN("id") &&
|
|
|
|
strcasecmp(lwc_string_data(name), "id") == 0) {
|
|
|
|
p->id = lwc_string_ref(value);
|
|
|
|
} else if (p->nclasses == 0 &&
|
|
|
|
lwc_string_length(name) == SLEN("class") &&
|
|
|
|
strcasecmp(lwc_string_data(name),
|
|
|
|
"class") == 0) {
|
|
|
|
hubbub_error error;
|
|
|
|
|
|
|
|
error = parse_class_attr(value, &p->classes,
|
|
|
|
&p->nclasses);
|
|
|
|
if (error != HUBBUB_OK) {
|
|
|
|
lwc_string_unref(value);
|
|
|
|
lwc_string_unref(name);
|
|
|
|
return error;
|
|
|
|
}
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
2011-10-08 04:21:59 +04:00
|
|
|
|
|
|
|
lwc_string_unref(value);
|
|
|
|
lwc_string_unref(name);
|
|
|
|
|
2008-09-23 06:19:50 +04:00
|
|
|
if (prop == NULL) {
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_NOMEM;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error set_quirks_mode(void *ctx, hubbub_quirks_mode mode)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
2009-07-24 03:05:34 +04:00
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case HUBBUB_QUIRKS_MODE_NONE:
|
|
|
|
c->quirks = BINDING_QUIRKS_MODE_NONE;
|
|
|
|
break;
|
|
|
|
case HUBBUB_QUIRKS_MODE_LIMITED:
|
|
|
|
c->quirks = BINDING_QUIRKS_MODE_LIMITED;
|
|
|
|
break;
|
|
|
|
case HUBBUB_QUIRKS_MODE_FULL:
|
|
|
|
c->quirks = BINDING_QUIRKS_MODE_FULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-04-15 16:26:25 +04:00
|
|
|
hubbub_error change_encoding(void *ctx, const char *charset)
|
2008-09-23 06:19:50 +04:00
|
|
|
{
|
|
|
|
hubbub_ctx *c = (hubbub_ctx *) ctx;
|
2008-11-30 01:53:58 +03:00
|
|
|
uint32_t source;
|
|
|
|
const char *name;
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
/* If we have an encoding here, it means we are *certain* */
|
|
|
|
if (c->encoding != NULL) {
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Find the confidence otherwise (can only be from a BOM) */
|
2008-11-30 01:53:58 +03:00
|
|
|
name = hubbub_parser_read_charset(c->parser, &source);
|
2008-09-23 06:19:50 +04:00
|
|
|
|
|
|
|
if (source == HUBBUB_CHARSET_CONFIDENT) {
|
|
|
|
c->encoding_source = ENCODING_SOURCE_DETECTED;
|
|
|
|
c->encoding = (char *) charset;
|
2009-04-15 16:26:25 +04:00
|
|
|
return HUBBUB_OK;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* So here we have something of confidence tentative... */
|
|
|
|
/* http://www.whatwg.org/specs/web-apps/current-work/#change */
|
|
|
|
|
|
|
|
/* 2. "If the new encoding is identical or equivalent to the encoding
|
|
|
|
* that is already being used to interpret the input stream, then set
|
|
|
|
* the confidence to confident and abort these steps." */
|
|
|
|
|
|
|
|
/* Whatever happens, the encoding should be set here; either for
|
|
|
|
* reprocessing with a different charset, or for confirming that the
|
|
|
|
* charset is in fact correct */
|
|
|
|
c->encoding = charset;
|
|
|
|
c->encoding_source = ENCODING_SOURCE_META;
|
|
|
|
|
|
|
|
/* Equal encodings will have the same string pointers */
|
2009-04-15 16:26:25 +04:00
|
|
|
return (charset == name) ? HUBBUB_OK : HUBBUB_ENCODINGCHANGE;
|
2008-09-23 06:19:50 +04:00
|
|
|
}
|
|
|
|
|
2009-02-20 14:39:25 +03:00
|
|
|
struct form *parse_form_element(xmlNode *node, const char *docenc)
|
|
|
|
{
|
|
|
|
struct form *form;
|
|
|
|
form_method method;
|
|
|
|
xmlChar *action, *meth, *charset, *target;
|
|
|
|
|
|
|
|
action = xmlGetProp(node, (const xmlChar *) "action");
|
|
|
|
charset = xmlGetProp(node, (const xmlChar *) "accept-charset");
|
|
|
|
target = xmlGetProp(node, (const xmlChar *) "target");
|
|
|
|
|
|
|
|
method = method_GET;
|
|
|
|
meth = xmlGetProp(node, (const xmlChar *) "method");
|
|
|
|
if (meth != NULL) {
|
|
|
|
if (strcasecmp((char *) meth, "post") == 0) {
|
|
|
|
xmlChar *enctype;
|
|
|
|
|
|
|
|
method = method_POST_URLENC;
|
|
|
|
|
|
|
|
enctype = xmlGetProp(node, (const xmlChar *) "enctype");
|
|
|
|
if (enctype != NULL) {
|
|
|
|
if (strcasecmp((char *) enctype,
|
|
|
|
"multipart/form-data") == 0)
|
|
|
|
method = method_POST_MULTIPART;
|
|
|
|
|
|
|
|
xmlFree(enctype);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
xmlFree(meth);
|
|
|
|
}
|
|
|
|
|
|
|
|
form = form_new(node, (char *) action, (char *) target, method,
|
|
|
|
(char *) charset, docenc);
|
|
|
|
|
|
|
|
if (target != NULL)
|
|
|
|
xmlFree(target);
|
|
|
|
if (charset != NULL)
|
|
|
|
xmlFree(charset);
|
|
|
|
if (action != NULL)
|
|
|
|
xmlFree(action);
|
|
|
|
|
|
|
|
return form;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct form_control *parse_input_element(xmlNode *node)
|
|
|
|
{
|
|
|
|
struct form_control *control = NULL;
|
|
|
|
xmlChar *type = xmlGetProp(node, (const xmlChar *) "type");
|
|
|
|
xmlChar *name;
|
|
|
|
|
|
|
|
if (type != NULL && strcasecmp((char *) type, "password") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_PASSWORD);
|
|
|
|
} else if (type != NULL && strcasecmp((char *) type, "file") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_FILE);
|
|
|
|
} else if (type != NULL && strcasecmp((char *) type, "hidden") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_HIDDEN);
|
|
|
|
} else if (type != NULL && strcasecmp((char *) type, "checkbox") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_CHECKBOX);
|
|
|
|
} else if (type != NULL && strcasecmp((char *) type, "radio") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_RADIO);
|
|
|
|
} else if (type != NULL && strcasecmp((char *) type, "submit") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_SUBMIT);
|
|
|
|
} else if (type != NULL && strcasecmp((char *) type, "reset") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_RESET);
|
|
|
|
} else if (type != NULL && strcasecmp((char *) type, "button") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_BUTTON);
|
|
|
|
} else if (type != NULL && strcasecmp((char *) type, "image") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_IMAGE);
|
|
|
|
} else {
|
|
|
|
control = form_new_control(node, GADGET_TEXTBOX);
|
|
|
|
}
|
|
|
|
|
|
|
|
xmlFree(type);
|
|
|
|
|
|
|
|
if (control == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (control->type == GADGET_CHECKBOX || control->type == GADGET_RADIO) {
|
|
|
|
control->selected =
|
2009-04-16 16:49:49 +04:00
|
|
|
xmlHasProp(node, (const xmlChar *) "checked") != NULL;
|
2009-02-20 14:39:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (control->type == GADGET_PASSWORD ||
|
|
|
|
control->type == GADGET_TEXTBOX) {
|
|
|
|
xmlChar *len = xmlGetProp(node, (const xmlChar *) "maxlength");
|
|
|
|
if (len != NULL) {
|
|
|
|
if (len[0] != '\0')
|
|
|
|
control->maxlength = atoi((char *) len);
|
|
|
|
xmlFree(len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (control->type != GADGET_FILE && control->type != GADGET_IMAGE) {
|
|
|
|
xmlChar *value = xmlGetProp(node, (const xmlChar *) "value");
|
|
|
|
if (value != NULL) {
|
|
|
|
control->value = strdup((char *) value);
|
|
|
|
|
|
|
|
xmlFree(value);
|
|
|
|
|
|
|
|
if (control->value == NULL) {
|
|
|
|
form_free_control(control);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
control->length = strlen(control->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (control->type == GADGET_TEXTBOX ||
|
|
|
|
control->type == GADGET_PASSWORD) {
|
|
|
|
if (control->value == NULL) {
|
|
|
|
control->value = strdup("");
|
|
|
|
if (control->value == NULL) {
|
|
|
|
form_free_control(control);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
control->length = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
control->initial_value = strdup(control->value);
|
|
|
|
if (control->initial_value == NULL) {
|
|
|
|
form_free_control(control);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
name = xmlGetProp(node, (const xmlChar *) "name");
|
|
|
|
if (name != NULL) {
|
|
|
|
control->name = strdup((char *) name);
|
|
|
|
|
|
|
|
xmlFree(name);
|
|
|
|
|
|
|
|
if (control->name == NULL) {
|
|
|
|
form_free_control(control);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return control;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct form_control *parse_button_element(xmlNode *node)
|
|
|
|
{
|
|
|
|
struct form_control *control;
|
|
|
|
xmlChar *type = xmlGetProp(node, (const xmlChar *) "type");
|
|
|
|
xmlChar *name;
|
|
|
|
xmlChar *value;
|
|
|
|
|
|
|
|
if (type == NULL || strcasecmp((char *) type, "submit") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_SUBMIT);
|
|
|
|
} else if (strcasecmp((char *) type, "reset") == 0) {
|
|
|
|
control = form_new_control(node, GADGET_RESET);
|
|
|
|
} else {
|
|
|
|
control = form_new_control(node, GADGET_BUTTON);
|
|
|
|
}
|
|
|
|
|
|
|
|
xmlFree(type);
|
|
|
|
|
|
|
|
if (control == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
value = xmlGetProp(node, (const xmlChar *) "value");
|
|
|
|
if (value != NULL) {
|
|
|
|
control->value = strdup((char *) value);
|
|
|
|
|
|
|
|
xmlFree(value);
|
|
|
|
|
|
|
|
if (control->value == NULL) {
|
|
|
|
form_free_control(control);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
name = xmlGetProp(node, (const xmlChar *) "name");
|
|
|
|
if (name != NULL) {
|
|
|
|
control->name = strdup((char *) name);
|
|
|
|
|
|
|
|
xmlFree(name);
|
|
|
|
|
|
|
|
if (control->name == NULL) {
|
|
|
|
form_free_control(control);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return control;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct form_control *parse_select_element(xmlNode *node)
|
|
|
|
{
|
|
|
|
struct form_control *control = form_new_control(node, GADGET_SELECT);
|
|
|
|
xmlChar *name;
|
|
|
|
|
|
|
|
if (control == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
control->data.select.multiple =
|
2009-04-16 16:49:49 +04:00
|
|
|
xmlHasProp(node, (const xmlChar *) "multiple") != NULL;
|
2009-02-20 14:39:25 +03:00
|
|
|
|
|
|
|
name = xmlGetProp(node, (const xmlChar *) "name");
|
|
|
|
if (name != NULL) {
|
|
|
|
control->name = strdup((char *) name);
|
|
|
|
|
|
|
|
xmlFree(name);
|
|
|
|
|
|
|
|
if (control->name == NULL) {
|
|
|
|
form_free_control(control);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return control;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct form_control *parse_textarea_element(xmlNode *node)
|
|
|
|
{
|
|
|
|
struct form_control *control = form_new_control(node, GADGET_TEXTAREA);
|
|
|
|
xmlChar *name;
|
|
|
|
|
|
|
|
if (control == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
name = xmlGetProp(node, (const xmlChar *) "name");
|
|
|
|
if (name != NULL) {
|
|
|
|
control->name = strdup((char *) name);
|
|
|
|
|
|
|
|
xmlFree(name);
|
|
|
|
|
|
|
|
if (control->name == NULL) {
|
|
|
|
form_free_control(control);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return control;
|
|
|
|
}
|
|
|
|
|