njs_str_t *name, njs_value_t *setval, njs_value_t *retval);
static xmlNode *njs_xml_external_node(njs_vm_t *vm, njs_value_t *value);
-static njs_int_t njs_xml_str_to_c_string(njs_vm_t *vm, njs_str_t *str,
+static const u_char *njs_xml_string_to_c_string(njs_vm_t *vm, njs_str_t *str,
u_char *dst, size_t size);
-static const u_char *njs_xml_value_to_c_string(njs_vm_t *vm, njs_value_t *value,
- u_char *dst, size_t size);
-static njs_int_t njs_xml_replace_node(njs_vm_t *vm, njs_value_t *value,
- xmlNode *current);
+static njs_bool_t njs_xml_node_is_live(xmlNode *node);
+static xmlNode *njs_xml_list_detach(xmlNode *parent, njs_str_t *name);
+static njs_int_t njs_xml_list_retire(njs_vm_t *vm, xmlNode *parent,
+ njs_str_t *name);
+static void njs_xml_list_append(xmlNode *parent, xmlNode *node);
+static njs_bool_t njs_xml_tree_has_namespaces(xmlNode *node);
static void njs_xml_node_cleanup(void *data);
static void njs_xml_doc_cleanup(void *data);
njs_xml_node_ext_add_child(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
{
- xmlNode *current, *node, *copy, *rnode;
- njs_int_t ret;
+ xmlNode *current, *node;
current = njs_vm_external(vm, njs_xml_node_proto_id, njs_argument(args, 0));
- if (njs_slow_path(current == NULL)) {
+ if (njs_slow_path(!njs_xml_node_is_live(current))) {
njs_vm_type_error(vm, "\"this\" is not a XMLNode object");
return NJS_ERROR;
}
return NJS_ERROR;
}
- copy = xmlDocCopyNode(current, current->doc, 1);
- if (njs_slow_path(copy == NULL)) {
- njs_vm_internal_error(vm, "xmlDocCopyNode() failed");
+ if (njs_slow_path(njs_xml_tree_has_namespaces(node))) {
+ njs_vm_type_error(vm, "XMLNode has namespaces");
return NJS_ERROR;
}
node = xmlDocCopyNode(node, current->doc, 1);
if (njs_slow_path(node == NULL)) {
njs_vm_internal_error(vm, "xmlDocCopyNode() failed");
- goto error;
- }
-
- rnode = xmlAddChild(copy, node);
- if (njs_slow_path(rnode == NULL)) {
- xmlFreeNode(node);
- njs_vm_internal_error(vm, "xmlAddChild() failed");
- goto error;
+ return NJS_ERROR;
}
- ret = xmlReconciliateNs(current->doc, copy);
- if (njs_slow_path(ret == -1)) {
- njs_vm_internal_error(vm, "xmlReconciliateNs() failed");
- goto error;
- }
+ njs_xml_list_append(current, node);
njs_value_undefined_set(retval);
- return njs_xml_replace_node(vm, njs_argument(args, 0), copy);
-
-error:
-
- xmlFreeNode(copy);
-
- return NJS_ERROR;
+ return NJS_OK;
}
xmlNode *current;
current = njs_vm_external(vm, njs_xml_node_proto_id, njs_argument(args, 0));
- if (njs_slow_path(current == NULL)) {
+ if (njs_slow_path(!njs_xml_node_is_live(current))) {
njs_vm_type_error(vm, "\"this\" is not a XMLNode object");
return NJS_ERROR;
}
njs_xml_node_ext_remove_children(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
{
- xmlNode *current, *copy;
+ xmlNode *current;
njs_str_t name;
njs_value_t *selector;
current = njs_vm_external(vm, njs_xml_node_proto_id, njs_argument(args, 0));
- if (njs_slow_path(current == NULL)) {
+ if (njs_slow_path(!njs_xml_node_is_live(current))) {
njs_vm_type_error(vm, "\"this\" is not a XMLNode object");
return NJS_ERROR;
}
/* all. */
- copy = xmlDocCopyNode(current, current->doc, 1);
- if (njs_slow_path(copy == NULL)) {
- njs_vm_internal_error(vm, "xmlDocCopyNode() failed");
- return NJS_ERROR;
- }
-
- if (copy->children != NULL) {
- xmlFreeNodeList(copy->children);
- copy->children = NULL;
+ if (current->children == NULL) {
+ return NJS_OK;
}
- return njs_xml_replace_node(vm, njs_argument(args, 0), copy);
+ return njs_xml_list_retire(vm, current, NULL);
}
njs_str_t name;
current = njs_vm_external(vm, njs_xml_node_proto_id, value);
- if (njs_slow_path(current == NULL || current->children == NULL)) {
+ if (njs_slow_path(current == NULL)) {
+ njs_value_undefined_set(retval);
+ return NJS_DECLINED;
+ }
+
+ if (retval != NULL && current->children == NULL && setval == NULL) {
njs_value_undefined_set(retval);
return NJS_DECLINED;
}
njs_xml_node_ext_text(njs_vm_t *vm, njs_object_prop_t *unused, uint32_t unused1,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
- u_char *text;
- xmlNode *current, *copy, *node;
+ xmlNode *current, *text;
njs_int_t ret;
njs_str_t content;
}
if (retval != NULL && setval == NULL) {
- text = xmlNodeGetContent(current);
- ret = njs_vm_value_string_create(vm, retval, text, njs_strlen(text));
+ content.start = xmlNodeGetContent(current);
+ ret = njs_vm_value_string_create(vm, retval, content.start,
+ njs_strlen(content.start));
- xmlFree(text);
+ xmlFree(content.start);
return ret;
}
+ if (njs_slow_path(!njs_xml_node_is_live(current))) {
+ njs_vm_type_error(vm, "XMLNode is detached");
+ return NJS_ERROR;
+ }
+
/* set or delete. */
content.start = NULL;
}
}
- copy = xmlDocCopyNode(current, current->doc, 1);
- if (njs_slow_path(copy == NULL)) {
- njs_vm_internal_error(vm, "xmlDocCopyNode() failed");
- return NJS_ERROR;
- }
+ text = NULL;
- node = NULL;
if (retval != NULL && setval != NULL) {
- node = xmlNewDocTextLen(current->doc, content.start, content.length);
- if (njs_slow_path(node == NULL)) {
- xmlFreeNode(copy);
+ text = xmlNewDocTextLen(current->doc, content.start, content.length);
+ if (njs_slow_path(text == NULL)) {
njs_vm_internal_error(vm, "xmlNewDocTextLen() failed");
return NJS_ERROR;
}
}
- xmlFreeNodeList(copy->children);
- copy->children = node;
- copy->last = node;
+ if (current->children != NULL) {
+ if (njs_slow_path(njs_xml_list_retire(vm, current, NULL) != NJS_OK)) {
+ xmlFreeNode(text);
+ return NJS_ERROR;
+ }
+ }
- if (node != NULL) {
- node->parent = copy;
- node->prev = NULL;
- node->next = NULL;
+ if (text != NULL) {
+ njs_xml_list_append(current, text);
}
if (retval != NULL) {
njs_value_undefined_set(retval);
}
- return njs_xml_replace_node(vm, value, copy);
+ return NJS_OK;
}
{
size_t size;
njs_int_t ret;
+ njs_str_t str;
xmlAttr *attr;
- const u_char *content, *value;
+ const u_char *content, *name_c, *value;
u_char name_buf[512], value_buf[1024];
if (retval != NULL && setval == NULL) {
/* set or delete. */
- ret = njs_xml_str_to_c_string(vm, name, &name_buf[0], sizeof(name_buf));
- if (njs_slow_path(ret != NJS_OK)) {
+ if (njs_slow_path(!njs_xml_node_is_live(current))) {
+ njs_vm_type_error(vm, "XMLNode is detached");
+ return NJS_ERROR;
+ }
+
+ name_c = njs_xml_string_to_c_string(vm, name, &name_buf[0],
+ sizeof(name_buf));
+ if (njs_slow_path(name_c == NULL)) {
return NJS_ERROR;
}
- ret = xmlValidateQName(&name_buf[0], 0);
+ ret = xmlValidateQName(name_c, 0);
if (njs_slow_path(ret != 0)) {
njs_vm_type_error(vm, "attribute name \"%V\" is not valid", name);
return NJS_ERROR;
{
/* delete. */
- attr = xmlHasProp(current, &name_buf[0]);
+ attr = xmlHasProp(current, name_c);
if (attr != NULL) {
xmlRemoveProp(attr);
return NJS_OK;
}
- value = njs_xml_value_to_c_string(vm, setval, &value_buf[0],
- sizeof(value_buf));
+ if (njs_slow_path(!njs_value_is_string(setval))) {
+ njs_vm_type_error(vm, "setval is not a string");
+ return NJS_ERROR;
+ }
+
+ njs_value_string_get(vm, setval, &str);
+
+ value = njs_xml_string_to_c_string(vm, &str, &value_buf[0],
+ sizeof(value_buf));
if (njs_slow_path(value == NULL)) {
return NJS_ERROR;
}
- attr = xmlSetProp(current, &name_buf[0], value);
+ attr = xmlSetProp(current, name_c, value);
if (njs_slow_path(attr == NULL)) {
njs_vm_internal_error(vm, "xmlSetProp() failed");
return NJS_ERROR;
static njs_int_t
njs_xml_node_tag_remove(njs_vm_t *vm, njs_value_t *value, njs_str_t *name)
{
- size_t size;
- xmlNode *current, *node, *next, *copy;
+ xmlNode *current, *node;
current = njs_vm_external(vm, njs_xml_node_proto_id, value);
- copy = xmlDocCopyNode(current, current->doc, 1);
- if (njs_slow_path(copy == NULL)) {
- njs_vm_internal_error(vm, "xmlDocCopyNode() failed");
+ if (njs_slow_path(!njs_xml_node_is_live(current))) {
+ njs_vm_type_error(vm, "XMLNode is detached");
return NJS_ERROR;
}
- for (node = copy->children; node != NULL; node = next) {
- next = node->next;
-
- if (node->type != XML_ELEMENT_NODE) {
- continue;
- }
-
- size = njs_strlen(node->name);
-
- if (name->length != size
- || njs_strncmp(name->start, node->name, size) != 0)
+ for (node = current->children; node != NULL; node = node->next) {
+ if (node->type == XML_ELEMENT_NODE
+ && name->length == njs_strlen(node->name)
+ && njs_strncmp(name->start, node->name, name->length) == 0)
{
- continue;
+ break;
}
+ }
- xmlUnlinkNode(node);
- xmlFreeNode(node);
+ if (node == NULL) {
+ return NJS_OK;
}
- return njs_xml_replace_node(vm, value, copy);
+ return njs_xml_list_retire(vm, current, name);
}
njs_xml_node_tags_handler(njs_vm_t *vm, njs_value_t *value, njs_str_t *name,
njs_value_t *setval, njs_value_t *retval)
{
- size_t size;
- int64_t i, length;
- xmlNode *current, *node, *rnode, *copy;
- njs_int_t ret;
- njs_value_t *push;
+ size_t size;
+ xmlNode *current, *first, *last, *node;
+ int64_t i, length;
+ njs_int_t ret;
+ njs_value_t *push;
njs_opaque_value_t *start;
current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+ first = NULL;
+ last = NULL;
+
if (retval != NULL && setval == NULL) {
/* get. */
/* set or delete. */
- copy = xmlDocCopyNode(current, current->doc,
- 2 /* copy properties and namespaces */);
- if (njs_slow_path(copy == NULL)) {
- njs_vm_internal_error(vm, "xmlDocCopyNode() failed");
- return NJS_ERROR;
- }
-
if (retval == NULL) {
/* delete. */
- return njs_xml_replace_node(vm, value, copy);
+ if (!njs_xml_node_is_live(current)) {
+ njs_vm_type_error(vm, "XMLNode is detached");
+ return NJS_ERROR;
+ }
+
+ if (current->children == NULL) {
+ return NJS_OK;
+ }
+
+ if (njs_slow_path(njs_xml_list_retire(vm, current, NULL) != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ return NJS_OK;
}
if (!njs_value_is_array(setval)) {
goto error;
}
+ if (njs_slow_path(!njs_xml_node_is_live(current))) {
+ njs_vm_type_error(vm, "XMLNode is detached");
+ return NJS_ERROR;
+ }
+
start = (njs_opaque_value_t *) njs_vm_array_start(vm, setval);
if (njs_slow_path(start == NULL)) {
goto error;
goto error;
}
+ if (njs_slow_path(njs_xml_tree_has_namespaces(node))) {
+ njs_vm_type_error(vm, "setval[%D] has namespaces", i);
+ goto error;
+ }
+
node = xmlDocCopyNode(node, current->doc, 1);
if (njs_slow_path(node == NULL)) {
njs_vm_internal_error(vm, "xmlDocCopyNode() failed");
goto error;
}
- rnode = xmlAddChild(copy, node);
- if (njs_slow_path(rnode == NULL)) {
- njs_vm_internal_error(vm, "xmlAddChild() failed");
- xmlFreeNode(node);
+ node->parent = NULL;
+ node->prev = last;
+ node->next = NULL;
+
+ if (last != NULL) {
+ last->next = node;
+
+ } else {
+ first = node;
+ }
+
+ last = node;
+ }
+
+ if (current->children != NULL) {
+ if (njs_slow_path(njs_xml_list_retire(vm, current, NULL) != NJS_OK)) {
goto error;
}
}
- ret = xmlReconciliateNs(current->doc, copy);
- if (njs_slow_path(ret == -1)) {
- njs_vm_internal_error(vm, "xmlReconciliateNs() failed");
- goto error;
+ current->children = first;
+ current->last = last;
+
+ for (node = first; node != NULL; node = node->next) {
+ node->parent = current;
}
njs_value_undefined_set(retval);
- return njs_xml_replace_node(vm, value, copy);
+ return NJS_OK;
error:
- xmlFreeNode(copy);
+ xmlFreeNodeList(first);
return NJS_ERROR;
}
}
-static njs_int_t
-njs_xml_str_to_c_string(njs_vm_t *vm, njs_str_t *str, u_char *dst,
+static const u_char *
+njs_xml_string_to_c_string(njs_vm_t *vm, njs_str_t *str, u_char *dst,
size_t size)
{
u_char *p;
- if (njs_slow_path(str->length > size - njs_length("\0"))) {
- njs_vm_internal_error(vm, "njs_xml_str_to_c_string() very long string, "
- "length >= %uz", size - njs_length("\0"));
- return NJS_ERROR;
+ if (str->length >= size) {
+ dst = njs_mp_alloc(njs_vm_memory_pool(vm), str->length + 1);
+ if (njs_slow_path(dst == NULL)) {
+ njs_vm_memory_error(vm);
+ return NULL;
+ }
}
p = njs_cpymem(dst, str->start, str->length);
*p = '\0';
- return NJS_OK;
+ return dst;
}
-static const u_char *
-njs_xml_value_to_c_string(njs_vm_t *vm, njs_value_t *value, u_char *dst,
- size_t size)
+static njs_bool_t
+njs_xml_node_is_live(xmlNode *node)
{
- u_char *p;
- njs_str_t str;
- njs_int_t ret;
+ xmlNode *parent;
- ret = njs_vm_value_to_bytes(vm, &str, value);
- if (njs_slow_path(ret != NJS_OK)) {
- return NULL;
+ if (node == NULL || node->doc == NULL) {
+ return 0;
}
- if (njs_fast_path(str.length + njs_length("\0") < size)) {
- ret = njs_xml_str_to_c_string(vm, &str, dst, size);
- if (njs_slow_path(ret != NJS_OK)) {
- return NULL;
+ for (parent = node->parent; parent != NULL; parent = parent->parent) {
+ if (parent == (xmlNode *) node->doc) {
+ return 1;
}
-
- return dst;
}
- dst = njs_mp_alloc(njs_vm_memory_pool(vm), str.length + njs_length("\0"));
- if (njs_slow_path(dst == NULL)) {
- njs_vm_memory_error(vm);
- return NULL;
+ return 0;
+}
+
+
+static xmlNode *
+njs_xml_list_detach(xmlNode *parent, njs_str_t *name)
+{
+ xmlNode *node, *next, *first, *last, *keep_first, *keep_last;
+
+ if (name == NULL) {
+ first = parent->children;
+ parent->children = NULL;
+ parent->last = NULL;
+
+ for (node = first; node != NULL; node = node->next) {
+ node->parent = NULL;
+ }
+
+ return first;
}
- p = njs_cpymem(dst, str.start, str.length);
- *p = '\0';
+ first = NULL;
+ last = NULL;
+ keep_first = NULL;
+ keep_last = NULL;
- return dst;
-}
+ for (node = parent->children; node != NULL; node = next) {
+ next = node->next;
+ if (node->type == XML_ELEMENT_NODE
+ && name->length == njs_strlen(node->name)
+ && njs_strncmp(name->start, node->name, name->length) == 0)
+ {
+ node->parent = NULL;
+ node->prev = last;
+ node->next = NULL;
-static njs_int_t
-njs_xml_replace_node(njs_vm_t *vm, njs_value_t *value, xmlNode *current)
-{
- xmlNode *old;
- njs_mp_cleanup_t *cln;
+ if (last != NULL) {
+ last->next = node;
+
+ } else {
+ first = node;
+ }
- old = njs_vm_external(vm, njs_xml_node_proto_id, value);
+ last = node;
- if (current != NULL) {
- old = xmlReplaceNode(old, current);
+ } else {
+ node->parent = parent;
+ node->prev = keep_last;
+ node->next = NULL;
- } else {
- xmlUnlinkNode(old);
+ if (keep_last != NULL) {
+ keep_last->next = node;
+
+ } else {
+ keep_first = node;
+ }
+
+ keep_last = node;
+ }
}
- njs_value_external_set(value, current);
+ parent->children = keep_first;
+ parent->last = keep_last;
+
+ return first;
+}
+
+
+static njs_int_t
+njs_xml_list_retire(njs_vm_t *vm, xmlNode *parent, njs_str_t *name)
+{
+ njs_mp_cleanup_t *cln;
cln = njs_mp_cleanup_add(njs_vm_memory_pool(vm), 0);
if (njs_slow_path(cln == NULL)) {
}
cln->handler = njs_xml_node_cleanup;
- cln->data = old;
+ cln->data = njs_xml_list_detach(parent, name);
return NJS_OK;
}
static void
-njs_xml_node_cleanup(void *data)
+njs_xml_list_append(xmlNode *parent, xmlNode *node)
{
- xmlNode *current = data;
+ node->parent = parent;
+ node->prev = parent->last;
+ node->next = NULL;
- xmlFreeNode(current);
+ if (parent->last != NULL) {
+ parent->last->next = node;
+
+ } else {
+ parent->children = node;
+ }
+
+ parent->last = node;
+}
+
+
+static njs_bool_t
+njs_xml_tree_has_namespaces(xmlNode *node)
+{
+ xmlAttr *attr;
+ xmlNode *child;
+
+ switch (node->type) {
+ case XML_ELEMENT_NODE:
+ break;
+
+ case XML_TEXT_NODE:
+ case XML_CDATA_SECTION_NODE:
+ case XML_PI_NODE:
+ case XML_COMMENT_NODE:
+ return 0;
+
+ default:
+ return 1;
+ }
+
+ if (node->ns != NULL || node->nsDef != NULL) {
+ return 1;
+ }
+
+ for (attr = node->properties; attr != NULL; attr = attr->next) {
+ if (attr->ns != NULL) {
+ return 1;
+ }
+ }
+
+ for (child = node->children; child != NULL; child = child->next) {
+ if (njs_xml_tree_has_namespaces(child)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static void
+njs_xml_node_cleanup(void *data)
+{
+ xmlFreeNodeList(data);
}
static void
njs_xml_doc_cleanup(void *data)
{
- njs_xml_doc_t *current = data;
+ njs_xml_doc_t *current;
+
+ current = data;
xmlFreeDoc(current->doc);
xmlFreeParserCtxt(current->ctx);
return NJS_ERROR;
}
+ if (njs_slow_path(!njs_xml_node_is_live(current))) {
+ njs_vm_type_error(vm, "XMLNode is detached");
+ return NJS_ERROR;
+ }
+
comments = njs_value_bool(njs_arg(args, nargs, 3));
excluding = njs_arg(args, nargs, 2);
return NJS_ERROR;
}
+ if (njs_slow_path(node->doc != current->doc
+ || !njs_xml_node_is_live(node)))
+ {
+ njs_vm_type_error(vm, "\"excluding\" is not in XMLNode tree");
+ return NJS_ERROR;
+ }
+
nset = njs_xml_nset_create(vm, current->doc, current,
XML_NSET_TREE_NO_COMMENTS);
if (njs_slow_path(nset == NULL)) {
#include <libxml/xpathInternals.h>
+typedef struct qjs_xml_retired_s qjs_xml_retired_t;
+
+struct qjs_xml_retired_s {
+ xmlNode *nodes;
+ qjs_xml_retired_t *next;
+};
+
+
typedef struct {
- xmlDoc *doc;
- xmlParserCtxt *ctx;
- xmlNode *free;
- int ref_count;
+ xmlDoc *doc;
+ xmlParserCtxt *ctx;
+ qjs_xml_retired_t *retired;
+ int ref_count;
} qjs_xml_doc_t;
static qjs_xml_nset_t *qjs_xml_nset_add(qjs_xml_nset_t *nset,
qjs_xml_nset_t *add);
static void qjs_xml_nset_free(JSContext *cx, qjs_xml_nset_t *nset);
-static void qjs_xml_replace_node(JSContext *cx, qjs_xml_node_t *node,
- xmlNode *current);
+static int qjs_xml_node_is_live(xmlNode *node);
+static xmlNode *qjs_xml_list_detach(xmlNode *parent, njs_str_t *name);
+static int qjs_xml_list_retire(JSContext *cx, qjs_xml_doc_t *doc,
+ xmlNode *parent, njs_str_t *name);
+static void qjs_xml_list_append(xmlNode *parent, xmlNode *node);
+static int qjs_xml_tree_has_namespaces(xmlNode *node);
static void qjs_xml_error(JSContext *cx, qjs_xml_doc_t *current,
const char *fmt, ...);
return JS_EXCEPTION;
}
+ if (!qjs_xml_node_is_live(node)) {
+ JS_ThrowTypeError(cx, "XMLNode is detached");
+ return JS_EXCEPTION;
+ }
+
comments = JS_ToBool(cx, argv[2]);
if (comments < 0) {
return JS_EXCEPTION;
return JS_EXCEPTION;
}
+ if (nd->doc->doc != doc
+ || !qjs_xml_node_is_live(nd->node))
+ {
+ JS_ThrowTypeError(cx, "\"excluding\" is not in XMLNode tree");
+ return JS_EXCEPTION;
+ }
+
nset = qjs_xml_nset_create(cx, doc, node, XML_NSET_TREE_NO_COMMENTS);
if (nset == NULL) {
return JS_ThrowOutOfMemory(cx);
static void
qjs_xml_doc_free(JSRuntime *rt, qjs_xml_doc_t *current)
{
- xmlNode *node, *next;
+ qjs_xml_retired_t *retired, *next;
if (--current->ref_count > 0) {
return;
}
- node = current->free;
-
- while (node != NULL) {
- next = node->next;
- xmlFreeNode(node);
- node = next;
+ for (retired = current->retired; retired != NULL; retired = next) {
+ next = retired->next;
+ xmlFreeNodeList(retired->nodes);
+ js_free_rt(rt, retired);
}
if (current->doc != NULL) {
return -1;
}
+ if (!qjs_xml_node_is_live(node->node)) {
+ JS_ThrowTypeError(cx, "XMLNode is detached");
+ return -1;
+ }
+
if (xmlValidateQName(name, 0) != 0) {
JS_ThrowTypeError(cx, "attribute name \"%s\" is not valid", name);
return -1;
return 1;
}
+ if (!JS_IsString(setval)) {
+ JS_ThrowTypeError(cx, "setval is not a string");
+ return -1;
+ }
+
value = (const u_char *) JS_ToCString(cx, setval);
if (value == NULL) {
return -1;
qjs_xml_node_tag_modify(JSContext *cx, JSValue obj, njs_str_t *name,
JSValue setval)
{
- size_t size;
- xmlNode *node, *next, *copy;
+ xmlNode *node;
qjs_xml_node_t *current;
current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE);
return -1;
}
+ if (!qjs_xml_node_is_live(current->node)) {
+ JS_ThrowTypeError(cx, "XMLNode is detached");
+ return -1;
+ }
+
if (!JS_IsNullOrUndefined(setval)) {
JS_ThrowTypeError(cx, "XMLNode.$tag$xxx is not assignable, "
"use addChild() or node.$tags = [node1, node2, ..] "
return -1;
}
- copy = xmlDocCopyNode(current->node, current->doc->doc, 1);
- if (copy == NULL) {
- JS_ThrowInternalError(cx, "xmlDocCopyNode() failed");
- return -1;
- }
-
- for (node = copy->children; node != NULL; node = next) {
- next = node->next;
-
- if (node->type != XML_ELEMENT_NODE) {
- continue;
+ if (name->length == 0) {
+ if (current->node->children != NULL) {
+ return qjs_xml_list_retire(cx, current->doc, current->node, NULL);
}
- size = njs_strlen(node->name);
+ return 1;
+ }
- if (name->length > 0
- && (name->length != size
- || njs_strncmp(name->start, node->name, size) != 0))
+ for (node = current->node->children; node != NULL; node = node->next) {
+ if (node->type == XML_ELEMENT_NODE
+ && name->length == njs_strlen(node->name)
+ && njs_strncmp(name->start, node->name, name->length) == 0)
{
- continue;
+ break;
}
-
- xmlUnlinkNode(node);
-
- node->next = current->doc->free;
- current->doc->free = node;
}
- qjs_xml_replace_node(cx, current, copy);
+ if (node != NULL) {
+ return qjs_xml_list_retire(cx, current->doc, current->node, name);
+ }
return 1;
}
qjs_xml_node_tags_modify(JSContext *cx, JSValue obj, njs_str_t *name,
JSValue setval)
{
- int32_t len, i;
- xmlNode *node, *rnode, *copy;
JSValue length, v;
+ xmlNode *first, *last, *node;
+ uint32_t len, i;
qjs_xml_node_t *current;
current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE);
return -1;
}
+ if (!qjs_xml_node_is_live(current->node)) {
+ JS_ThrowTypeError(cx, "XMLNode is detached");
+ return -1;
+ }
+
+ if (name->length > 0) {
+ JS_ThrowTypeError(cx, "XMLNode $tags$xxx is not assignable, use "
+ "addChild() or node.$tags = [node1, node2, ..] "
+ "syntax");
+ return -1;
+ }
+
+ if (JS_IsNullOrUndefined(setval)) {
+ if (current->node->children == NULL) {
+ return 1;
+ }
+
+ return qjs_xml_list_retire(cx, current->doc, current->node, NULL);
+ }
+
if (!qjs_is_array(cx, setval)) {
JS_ThrowTypeError(cx, "setval is not an array");
return -1;
return -1;
}
- if (JS_ToInt32(cx, &len, length) < 0) {
+ if (JS_ToUint32(cx, &len, length) < 0) {
+ JS_FreeValue(cx, length);
return -1;
}
- copy = xmlDocCopyNode(current->node, current->doc->doc,
- 2 /* copy properties and namespaces */);
- if (copy == NULL) {
- JS_ThrowInternalError(cx, "xmlDocCopyNode() failed");
- return -1;
- }
+ JS_FreeValue(cx, length);
+
+ first = NULL;
+ last = NULL;
for (i = 0; i < len; i++) {
v = JS_GetPropertyUint32(cx, setval, i);
goto error;
}
+ if (qjs_xml_tree_has_namespaces(node)) {
+ JS_ThrowTypeError(cx, "setval[%u] has namespaces", i);
+ goto error;
+ }
+
node = xmlDocCopyNode(node, current->doc->doc, 1);
if (node == NULL) {
JS_ThrowInternalError(cx, "xmlDocCopyNode() failed");
goto error;
}
- rnode = xmlAddChild(copy, node);
- if (rnode == NULL) {
- xmlFreeNode(node);
- JS_ThrowInternalError(cx, "xmlAddChild() failed");
- goto error;
+ node->parent = NULL;
+ node->prev = last;
+ node->next = NULL;
+
+ if (last != NULL) {
+ last->next = node;
+
+ } else {
+ first = node;
}
+
+ last = node;
}
- if (xmlReconciliateNs(current->doc->doc, copy) == -1) {
- JS_ThrowInternalError(cx, "xmlReconciliateNs() failed");
+ if (!qjs_xml_node_is_live(current->node)) {
+ JS_ThrowTypeError(cx, "XMLNode is detached");
goto error;
}
- qjs_xml_replace_node(cx, current, copy);
+ if (current->node->children != NULL) {
+ if (qjs_xml_list_retire(cx, current->doc, current->node, NULL) < 0) {
+ goto error;
+ }
+ }
+
+ current->node->children = first;
+ current->node->last = last;
+
+ for (node = first; node != NULL; node = node->next) {
+ node->parent = current->node;
+ }
return 1;
error:
- xmlFreeNode(copy);
+ xmlFreeNodeList(first);
return -1;
}
static int
qjs_xml_node_text_handler(JSContext *cx, JSValue current, JSValue setval)
{
- xmlNode *copy, *text;
+ xmlNode *text;
njs_str_t content;
qjs_xml_node_t *node;
return -1;
}
+ if (!qjs_xml_node_is_live(node->node)) {
+ JS_ThrowTypeError(cx, "XMLNode is detached");
+ return -1;
+ }
+
if (!JS_IsNullOrUndefined(setval)) {
+ if (!JS_IsString(setval)) {
+ JS_ThrowTypeError(cx, "setval is not a string");
+ return -1;
+ }
+
content.start = (u_char *) JS_ToCStringLen(cx, &content.length, setval);
if (content.start == NULL) {
return -1;
}
}
- copy = xmlDocCopyNode(node->node, node->doc->doc, 1);
- if (copy == NULL) {
- if (!JS_IsNullOrUndefined(setval)) {
- JS_FreeCString(cx, (char *) content.start);
- }
-
- JS_ThrowInternalError(cx, "xmlDocCopyNode() failed");
- return -1;
- }
-
text = NULL;
if (!JS_IsNullOrUndefined(setval)) {
text = xmlNewDocTextLen(node->doc->doc, content.start, content.length);
JS_FreeCString(cx, (char *) content.start);
if (text == NULL) {
- xmlFreeNode(copy);
JS_ThrowInternalError(cx, "xmlNewDocTextLen() failed");
return -1;
}
}
- xmlFreeNodeList(copy->children);
- copy->children = text;
- copy->last = text;
+ if (node->node->children != NULL) {
+ if (qjs_xml_list_retire(cx, node->doc, node->node, NULL) < 0) {
+ xmlFreeNode(text);
+ return -1;
+ }
+ }
if (text != NULL) {
- text->parent = copy;
- text->prev = NULL;
- text->next = NULL;
+ qjs_xml_list_append(node->node, text);
}
- qjs_xml_replace_node(cx, node, copy);
-
return 1;
}
qjs_xml_node_add_child(JSContext *cx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
- xmlNode *copy, *node, *rnode;
+ xmlNode *node;
qjs_xml_node_t *current;
current = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_XML_NODE);
return JS_EXCEPTION;
}
+ if (!qjs_xml_node_is_live(current->node)) {
+ JS_ThrowTypeError(cx, "XMLNode is detached");
+ return JS_EXCEPTION;
+ }
+
node = qjs_xml_node(cx, argv[0], NULL);
if (node == NULL) {
return JS_EXCEPTION;
}
- copy = xmlDocCopyNode(current->node, current->doc->doc, 1);
- if (copy == NULL) {
- JS_ThrowInternalError(cx, "xmlDocCopyNode() failed");
+ if (qjs_xml_tree_has_namespaces(node)) {
+ JS_ThrowTypeError(cx, "XMLNode has namespaces");
return JS_EXCEPTION;
}
node = xmlDocCopyNode(node, current->doc->doc, 1);
if (node == NULL) {
JS_ThrowInternalError(cx, "xmlDocCopyNode() failed");
- goto error;
- }
-
- rnode = xmlAddChild(copy, node);
- if (rnode == NULL) {
- xmlFreeNode(node);
- JS_ThrowInternalError(cx, "xmlAddChild() failed");
- goto error;
- }
-
- if (xmlReconciliateNs(current->doc->doc, copy) == -1) {
- JS_ThrowInternalError(cx, "xmlReconciliateNs() failed");
- goto error;
+ return JS_EXCEPTION;
}
- qjs_xml_replace_node(cx, current, copy);
+ qjs_xml_list_append(current->node, node);
return JS_UNDEFINED;
-
-error:
-
- xmlFreeNode(copy);
-
- return JS_EXCEPTION;
}
return JS_EXCEPTION;
}
+ if (!qjs_xml_node_is_live(current->node)) {
+ JS_ThrowTypeError(cx, "XMLNode is detached");
+ return JS_EXCEPTION;
+ }
+
node = current->node;
if (node->properties != NULL) {
}
-static void
-qjs_xml_replace_node(JSContext *cx, qjs_xml_node_t *node, xmlNode *current)
+static int
+qjs_xml_node_is_live(xmlNode *node)
+{
+ xmlNode *parent;
+
+ if (node == NULL || node->doc == NULL) {
+ return 0;
+ }
+
+ for (parent = node->parent; parent != NULL; parent = parent->parent) {
+ if (parent == (xmlNode *) node->doc) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static xmlNode *
+qjs_xml_list_detach(xmlNode *parent, njs_str_t *name)
+{
+ xmlNode *node, *next, *first, *last, *keep_first, *keep_last;
+
+ if (name == NULL) {
+ first = parent->children;
+ parent->children = NULL;
+ parent->last = NULL;
+
+ for (node = first; node != NULL; node = node->next) {
+ node->parent = NULL;
+ }
+
+ return first;
+ }
+
+ first = NULL;
+ last = NULL;
+ keep_first = NULL;
+ keep_last = NULL;
+
+ for (node = parent->children; node != NULL; node = next) {
+ next = node->next;
+
+ if (node->type == XML_ELEMENT_NODE
+ && name->length == njs_strlen(node->name)
+ && njs_strncmp(name->start, node->name, name->length) == 0)
+ {
+ node->parent = NULL;
+ node->prev = last;
+ node->next = NULL;
+
+ if (last != NULL) {
+ last->next = node;
+
+ } else {
+ first = node;
+ }
+
+ last = node;
+
+ } else {
+ node->parent = parent;
+ node->prev = keep_last;
+ node->next = NULL;
+
+ if (keep_last != NULL) {
+ keep_last->next = node;
+
+ } else {
+ keep_first = node;
+ }
+
+ keep_last = node;
+ }
+ }
+
+ parent->children = keep_first;
+ parent->last = keep_last;
+
+ return first;
+}
+
+
+static int
+qjs_xml_list_retire(JSContext *cx, qjs_xml_doc_t *doc, xmlNode *parent,
+ njs_str_t *name)
{
- xmlNode *old;
+ qjs_xml_retired_t *retired;
+
+ retired = js_malloc(cx, sizeof(qjs_xml_retired_t));
+ if (retired == NULL) {
+ JS_ThrowOutOfMemory(cx);
+ return -1;
+ }
+
+ retired->nodes = qjs_xml_list_detach(parent, name);
+ retired->next = doc->retired;
+ doc->retired = retired;
+
+ return 1;
+}
- old = node->node;
- if (current != NULL) {
- old = xmlReplaceNode(old, current);
+static void
+qjs_xml_list_append(xmlNode *parent, xmlNode *node)
+{
+ node->parent = parent;
+ node->prev = parent->last;
+ node->next = NULL;
+
+ if (parent->last != NULL) {
+ parent->last->next = node;
} else {
- xmlUnlinkNode(old);
+ parent->children = node;
}
- node->node = current;
+ parent->last = node;
+}
+
- old->next = node->doc->free;
- node->doc->free = old;
+static int
+qjs_xml_tree_has_namespaces(xmlNode *node)
+{
+ xmlAttr *attr;
+ xmlNode *child;
+
+ switch (node->type) {
+ case XML_ELEMENT_NODE:
+ break;
+
+ case XML_TEXT_NODE:
+ case XML_CDATA_SECTION_NODE:
+ case XML_PI_NODE:
+ case XML_COMMENT_NODE:
+ return 0;
+
+ default:
+ return 1;
+ }
+
+ if (node->ns != NULL || node->nsDef != NULL) {
+ return 1;
+ }
+
+ for (attr = node->properties; attr != NULL; attr = attr->next) {
+ if (attr->ns != NULL) {
+ return 1;
+ }
+ }
+
+ for (child = node->children; child != NULL; child = child->next) {
+ if (qjs_xml_tree_has_namespaces(child)) {
+ return 1;
+ }
+ }
+
+ return 0;
}
import xml from 'xml';
+function has_quickjs() {
+ return (typeof njs != 'undefined' && njs.engine == 'QuickJS');
+}
+
let parse_tsuite = {
name: "parse()",
skip: () => (!has_njs()),
{ doc: `<note><to a="foo" b="bar">Tove</to><from>Jani</from></note>`,
call: (doc) => xml.exclusiveC14n(doc.note, doc.note.to),
expected: `<note><from>Jani</from></note>` },
+ { doc: `<r><a>A</a></r>`,
+ call: (doc) => {
+ let other = xml.parse(`<other></other>`);
+ return xml.exclusiveC14n(doc.r, other.other);
+ },
+ exception: 'TypeError: "excluding" is not in XMLNode tree' },
+ { doc: `<r><a>A</a></r>`,
+ call: (doc) => {
+ let a = doc.r.a;
+ doc.r.removeChildren('a');
+ return xml.exclusiveC14n(doc.r, a);
+ },
+ exception: 'TypeError: "excluding" is not in XMLNode tree' },
{ doc: `<r></r>`,
call: (doc) => xml.exclusiveC14n(doc, 1),
exception: 'TypeError: "excluding" argument is not a XMLNode object' },
return doc.note.$text;
},
expected: 'WAKA' },
+ { get: () => {
+ let mutations = [
+ (node) => node.setText('first'),
+ (node) => node.removeText(),
+ (node) => node.removeChildren(),
+ (node) => node.removeChildren('a'),
+ (node, doc) => node.addChild(doc.r.b),
+ (node) => { delete node.$tag$a; },
+ (node, doc) => { node.$tags = [doc.r.a]; },
+ (node) => { delete node.$tags; },
+ ];
+ let passed = 0;
+
+ for (let i = 0; i < mutations.length; i++) {
+ for (let j = 0; j < 2; j++) {
+ let current = xml.parse(`<r><a>A</a><b>B</b></r>`);
+ let first = current.r;
+ let second = current.r;
+ let mutate = mutations[i];
+ let reverse = (j != 0);
+ let target = reverse ? second : first;
+ let alias = reverse ? first : second;
+
+ mutate(target, current);
+ alias.setText('final');
+
+ if (first.$text != 'final'
+ || second.$text != 'final'
+ || current.r.$text != 'final')
+ {
+ return false;
+ }
+
+ passed++;
+ }
+ }
+
+ return passed;
+ },
+ expected: 16 },
+ { get: (doc) => {
+ let direct = doc.note;
+ let root = doc.$root;
+ let parent = doc.note.to.$parent;
+ let tag = doc.note.$tag$to.$parent;
+ let tags = doc.note.$tags$to[0].$parent;
+
+ direct.setText('direct');
+ root.setText('root');
+ parent.setText('parent');
+ tag.setText('tag');
+ tags.setText('tags');
+
+ return [direct.$text, root.$text, parent.$text, tag.$text,
+ tags.$text, doc.note.$text].toString();
+ },
+ expected: 'tags,tags,tags,tags,tags,tags' },
+ { get: (doc) => {
+ let to = doc.note.to;
+ let attrs = to.$attrs;
+
+ to.setText('changed');
+ to.setAttribute('a', 'updated');
+
+ return [attrs.a, to.$text, doc.note.to.$text].toString();
+ },
+ expected: 'updated,changed,changed' },
{ get: (doc) => {
doc.note.setText('WAKA');
return xml.serializeToString(doc);
return 'missing exception';
},
expected: 'true,ToveJani' },
+ { get: (doc) => {
+ let called = false;
+ let value = {toString: () => {
+ called = true;
+ doc.note.removeChildren();
+ return 'changed';
+ }};
+
+ try {
+ doc.note.setText(value);
+ } catch (e) {
+ return [called, e instanceof TypeError,
+ xml.serializeToString(doc)].toString();
+ }
+
+ return 'missing exception';
+ },
+ expected: 'false,true,<note><to a="foo" b="bar">Tove</to><from>Jani</from></note>' },
{ get: (doc) => {
doc.note.setText(null);
return doc.note.$text;
{ get: (doc) => {
let to = doc.note.to;
doc.$root.$text = '';
- return to.$name;
- },
- expected: 'to' },
- { get: (doc) => {
- let to = doc.note.to;
- doc.$root.$text = '';
- return [to.$name, to.$text, to.$attr$b, to.$parent.$name].toString();
+ return [to.$name, to.$text, to.$attr$b, to.$parent].toString();
},
- expected: 'to,Tove,bar,note' },
+ expected: 'to,Tove,bar,' },
{ get: (doc) => {
doc.note.to.setAttribute('aaa', 'foo');
doc.note.to.setAttribute('bbb', '<bar\"');
return xml.serializeToString(doc.note.to);
},
expected: `<to a="foo" aaa="foo2" b="bar">Tove</to>` },
+ { get: (doc) => {
+ let value = 'x'.repeat(2048);
+ doc.note.to.setAttribute('aaa', value);
+ doc.note.to.$attr$bbb = value;
+ return [doc.note.to.$attr$aaa.length,
+ doc.note.to.$attr$bbb.length].toString();
+ },
+ expected: '2048,2048' },
+ { get: (doc) => {
+ let called = false;
+ let value = {toString: () => {
+ called = true;
+ doc.note.removeChildren('to');
+ return 'changed';
+ }};
+
+ try {
+ doc.note.to.setAttribute('aaa', value);
+ } catch (e) {
+ return [called, e instanceof TypeError,
+ xml.serializeToString(doc)].toString();
+ }
+
+ return 'missing exception';
+ },
+ expected: 'false,true,<note><to a="foo" b="bar">Tove</to><from>Jani</from></note>' },
{ get: (doc) => {
doc.note.to.removeAttribute('a');
return xml.serializeToString(doc.note.to);
doc.note.addChild(doc2);
return xml.serializeToString(doc);
},
- expected: `<note xmlns:n0="http://a"><to a="foo" b="bar">Tove</to><from>Jani</from><n0:pdu></n0:pdu></note>` },
+ exception: 'TypeError: XMLNode has namespaces' },
{ get: (doc) => {
- let doc2 = xml.parse(`<n0:pdu xmlns:n0=\"http://a\"></n0:pdu>`);
+ let doc2 = xml.parse(`<child>value</child>`);
doc.note.addChild(doc2);
doc.note.addChild(doc2);
return xml.serializeToString(doc);
},
- expected: `<note xmlns:n0="http://a"><to a="foo" b="bar">Tove</to><from>Jani</from><n0:pdu></n0:pdu><n0:pdu></n0:pdu></note>` },
+ expected: '<note><to a="foo" b="bar">Tove</to><from>Jani</from>'
+ + '<child>value</child><child>value</child></note>' },
{ get: (doc) => {
- doc.note.removeChildren('to');
- return xml.serializeToString(doc);
+ let source = xml.parse(`<!DOCTYPE root [<!ENTITY e "text">]><root>&e;</root>`);
+
+ try {
+ doc.note.addChild(source.root);
+ } catch (e) {
+ return e instanceof TypeError;
+ }
+
+ return false;
},
- expected: `<note><from>Jani</from></note>` },
+ expected: true },
{ get: (doc) => {
- let note = doc.note;
- note.removeChildren('to');
+ doc.note.removeChildren('to');
return xml.serializeToString(doc);
},
expected: `<note><from>Jani</from></note>` },
throw Error('unexpected exception');
},
expected: 'OK' },
- { doc: `<root><a>A</a><b>B</b><a>C</a></root>`,
+ { doc: `<root><a>A</a><b>B</b><c>C</c><a>D</a></root>`,
get: (doc) => {
+ let b = doc.root.b;
+ let c = doc.root.c;
doc.$root.removeChildren('a');
+ b.setText('changed');
+ c.setAttribute('x', 'y');
return xml.serializeToString(doc);
},
- expected: `<root><b>B</b></root>` },
+ expected: '<root><b>changed</b><c x="y">C</c></root>' },
{ doc: `<root><a>A</a><b>B</b><a>C</a></root>`,
get: (doc) => {
doc.$root.removeChildren();
return xml.serializeToString(doc);
},
expected: `<root></root>` },
+ { doc: `<root></root>`,
+ get: (doc) => {
+ delete doc.root.$tags;
+ return xml.serializeToString(doc);
+ },
+ expected: `<root></root>` },
+ { doc: `<root><a>A</a></root>`,
+ get: (doc) => {
+ delete doc.root.$tags;
+ return xml.serializeToString(doc);
+ },
+ expected: `<root></root>` },
+ { doc: `<root></root>`,
+ get: (doc) => {
+ let source = xml.parse(`<child>value</child>`);
+ doc.root.$tags = [source];
+ return xml.serializeToString(doc);
+ },
+ expected: `<root><child>value</child></root>` },
+ { doc: `<root><target><old>value</old></target></root>`,
+ skip: () => !has_quickjs(),
+ get: (doc) => {
+ let source = xml.parse(`<new></new>`);
+ let target = doc.root.target;
+ let values = [];
+
+ Object.defineProperty(values, '0', {
+ get: () => {
+ doc.root.removeChildren('target');
+ return source;
+ },
+ });
+ Object.defineProperty(values, 'length', {value: 1});
+
+ try {
+ target.$tags = values;
+ } catch (e) {
+ return [e instanceof TypeError, target.$parent,
+ xml.serializeToString(doc),
+ target.$name, target.$text].toString();
+ }
+
+ return 'missing exception';
+ },
+ expected: 'true,,<root></root>,target,value' },
+ { doc: `<root><a>A</a><b>B</b></root>`,
+ get: (doc) => {
+ let source = xml.parse(`<child/>`);
+
+ try {
+ doc.root.$tags$a = [source];
+ } catch (e) {
+ return [e instanceof TypeError, xml.serializeToString(doc)].toString();
+ }
+
+ return 'missing exception';
+ },
+ expected: 'true,<root><a>A</a><b>B</b></root>' },
+ { doc: `<root><a>A</a></root>`,
+ get: (doc) => {
+ let values = new Array(2147483648);
+
+ try {
+ doc.root.$tags = values;
+ } catch (e) {
+ return xml.serializeToString(doc);
+ }
+
+ return 'missing exception';
+ },
+ expected: '<root><a>A</a></root>' },
{ doc: `<root><a>A</a><b>B</b><a>C</a></root>`,
get: (doc) => {
delete doc.$root.$tag$a;
},
expected: `<root><b>B</b></root>` },
{ get: (doc) => {
- doc.note.$tags = [doc.note.to];
+ doc.note.$tags = [doc.note.to, doc.note.from];
return xml.serializeToString(doc);
},
- expected: `<note><to a="foo" b="bar">Tove</to></note>` },
+ expected: '<note><to a="foo" b="bar">Tove</to>'
+ + '<from>Jani</from></note>' },
{ get: (doc) => {
let doc2 = xml.parse(`<n0:pdu xmlns:n0=\"http://a\"></n0:pdu>`);
doc.note.$tags = [doc.note.to, doc2];
return xml.serializeToString(doc);
},
- expected: `<note xmlns:n0="http://a"><to a="foo" b="bar">Tove</to><n0:pdu></n0:pdu></note>` },
+ exception: 'TypeError: setval[1] has namespaces' },
{ get: (doc) => {
let doc2 = xml.parse(`<n0:pdu xmlns:n0=\"http://a\"></n0:pdu>`);
doc.note.$tags = [doc2, doc.note.to];
return xml.serializeToString(doc);
},
- expected: `<note xmlns:n0="http://a"><n0:pdu></n0:pdu><to a="foo" b="bar">Tove</to></note>` },
+ exception: 'TypeError: setval[0] has namespaces' },
+ { get: () => {
+ let mutations = [
+ (node) => node.addChild(xml.parse(`<new></new>`)),
+ (node) => node.removeChildren(),
+ (node) => node.removeChildren('child'),
+ (node) => { delete node.$tag$child; },
+ (node) => { node.$tags = []; },
+ (node) => { delete node.$tags; },
+ ];
+ let passed = 0;
+
+ for (let i = 0; i < mutations.length; i++) {
+ let current = xml.parse(
+ `<r><target><child>C</child></target></r>`);
+ let target = current.r.target;
+ let mutate = mutations[i];
+ current.r.removeChildren('target');
+
+ try {
+ mutate(target);
+
+ } catch (e) {
+ if (e instanceof TypeError
+ && target.$parent === undefined
+ && xml.serializeToString(current) == '<r></r>'
+ && target.$name == 'target'
+ && target.$text == 'C')
+ {
+ passed++;
+ continue;
+ }
+ }
+
+ return false;
+ }
+
+ return passed;
+ },
+ expected: 6 },
+ { doc: `<r><a><b>B</b></a></r>`,
+ get: (doc) => {
+ let a = doc.r.a;
+ let b = a.b;
+ doc.r.removeChildren('a');
+
+ try {
+ b.setText('changed');
+ } catch (e) {
+ return [a.$parent, b.$parent.$name, b.$text,
+ e instanceof TypeError].toString();
+ }
+
+ return 'missing exception';
+ },
+ expected: ',a,B,true' },
+ { get: (doc) => {
+ let to = doc.note.to;
+ doc.note.removeChildren('to');
+
+ try {
+ to.setText('changed');
+ } catch (e) {
+ return [to.$parent, to.$text, e instanceof TypeError].toString();
+ }
+
+ return 'missing exception';
+ },
+ expected: ',Tove,true' },
+ { get: (doc) => {
+ let to = doc.note.to;
+ doc.note.removeChildren('to');
+
+ try {
+ to.setAttribute('x', 'y');
+ } catch (e) {
+ return e instanceof TypeError;
+ }
+
+ return false;
+ },
+ expected: true },
+ { get: (doc) => {
+ let to = doc.note.to;
+ doc.note.removeChildren('to');
+
+ try {
+ to.removeAllAttributes();
+ } catch (e) {
+ return e instanceof TypeError;
+ }
+
+ return false;
+ },
+ expected: true },
+ { get: (doc) => {
+ let to = doc.note.to;
+ doc.note.removeChildren('to');
+
+ try {
+ xml.exclusiveC14n(to);
+ } catch (e) {
+ return e instanceof TypeError;
+ }
+
+ return false;
+ },
+ expected: true },
]};
run([