[njs] XML: added XMLNode API to modify XML documents.
Dmitry Volyntsev
xeioex at nginx.com
Thu Feb 23 04:13:43 UTC 2023
details: https://hg.nginx.org/njs/rev/3891f338e2c9
branches:
changeset: 2049:3891f338e2c9
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Wed Feb 22 19:13:08 2023 -0800
description:
XML: added XMLNode API to modify XML documents.
- delete node.$attr$a is a shorthand syntax for
node.removeAttribute('a')
- node.removeAllAttributes() removes all attributes of the node.
- node.removeAttribute(attr_name) removes attribute named
attr_name.
- node.setAttribute(attr_name, value || null) sets a value for an
attr_name. When value is null attribute named attr_name is
deleted.
- node.$attr$a = 'xxx' is a shorthand syntax for
node.setAttribute('a', 'xxx');
- The methods and operations below make copy-on-write
changes to the original XML parsed document.
For example:
var doc = xml.parse(<r><a><b/></a></r>);
var b = doc.$root.a.b;
doc.$root.removeAllChildren();
console.log((new TextDecoder()).decode(xml.c14n(doc))); /* <r></r> */
console.log(b); /* XMLNode {$name:'b'} */
console.log(b.$parent); /* XMLNode {$name:'a',$tags:[XMLNode
{$name:'b'}] */
"b" is valid after removeAllChildren() call, but is not a part
of the document tree anymore.
- node.addChild(nd) adds XMLNode as a child to node.
nd recursively copied before adding to the node.
- node.removeChildren(tag_name?) removes all the children tags
named tag_name. If tag_name is absent all children tags are
removed.
- node.removeText() removes the node's text value.
- node.setText(string || null) sets a text value for the node.
When value is null the node's text is deleted.
- node.$tags = [node1, node2, ..] is a shorthand syntax for
node.removeChildren();
node.addChild(node1);
node.addChild(node2)
- node.$text = 'xxx' is a shorthand syntax for
node.setText('xxx');
In addition the following method were added:
- xml.serialize() is the same as xml.c14n()
- xml.serializeToString() is the same as xml.c14n()
except it returns the result as string.
Example:
const xml = require("xml");
let data = `<note><to b="bar" a="foo">Tove</to><from>Jani</from></note>`;
let doc = xml.parse(data);
doc.$root.to.$attr$b = 'bar2';
doc.$root.to.setAttribute('c', 'baz');
delete doc.$root.to.$attr$a;
console.log(xml.serializeToString(doc.$root.to)) /* '<to b="bar2" c="baz">Tove</to>' */
doc.$root.to.removeAllAttributes();
doc.$root.from.$text = 'Jani2';
console.log(xml.serializeToString(doc)) /* '<note><to>Tove</to><from>Jani2</from></note>' */
doc.$root.to.$tags = [xml.parse(`<a/>`), xml.parse(`<b/>`)];
doc.$root.to.addChild(xml.parse(`<a/>`));
console.log(xml.serializeToString(doc.$root.to)) /* '<to><a></a><b></b><a></a></to>' */
doc.$root.to.removeChildren('a');
console.log(xml.serializeToString(doc.$root.to)) /* '<to><b></b></to>' */
diffstat:
external/njs_xml_module.c | 1064 ++++++++++++++++++++++++++++++++++++++------
src/test/njs_unit_test.c | 203 ++++++++-
test/xml/saml_verify.t.js | 105 +++-
ts/njs_modules/xml.d.ts | 42 +-
4 files changed, 1221 insertions(+), 193 deletions(-)
diffs (truncated from 1788 to 1000 lines):
diff -r 2af586015b65 -r 3891f338e2c9 external/njs_xml_module.c
--- a/external/njs_xml_module.c Fri Feb 17 22:38:25 2023 -0800
+++ b/external/njs_xml_module.c Wed Feb 22 19:13:08 2023 -0800
@@ -39,12 +39,11 @@ struct njs_xml_nset_s {
static njs_int_t njs_xml_ext_parse(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_int_t njs_xml_ext_canonicalization(njs_vm_t *vm, njs_value_t *args,
- njs_uint_t nargs, njs_index_t unused);
+ njs_uint_t nargs, njs_index_t magic);
static njs_int_t njs_xml_doc_ext_prop_keys(njs_vm_t *vm, njs_value_t *value,
njs_value_t *keys);
static njs_int_t njs_xml_doc_ext_root(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *unused, njs_value_t *retval);
-static void njs_xml_doc_cleanup(void *data);
static njs_int_t njs_xml_node_ext_prop_keys(njs_vm_t *vm, njs_value_t *value,
njs_value_t *keys);
static njs_int_t njs_xml_node_ext_prop_handler(njs_vm_t *vm,
@@ -55,6 +54,8 @@ static njs_int_t njs_xml_attr_ext_prop_k
static njs_int_t njs_xml_attr_ext_prop_handler(njs_vm_t *vm,
njs_object_prop_t *prop, njs_value_t *value, njs_value_t *unused,
njs_value_t *retval);
+static njs_int_t njs_xml_node_ext_add_child(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
static njs_int_t njs_xml_node_ext_attrs(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
static njs_int_t njs_xml_node_ext_name(njs_vm_t *vm, njs_object_prop_t *prop,
@@ -63,11 +64,44 @@ static njs_int_t njs_xml_node_ext_ns(njs
njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
static njs_int_t njs_xml_node_ext_parent(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_ext_remove_all_attributes(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_xml_node_ext_remove_attribute(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_xml_node_ext_remove_children(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_xml_node_ext_remove_text(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_xml_node_ext_set_attribute(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_xml_node_ext_set_text(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
static njs_int_t njs_xml_node_ext_tags(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
static njs_int_t njs_xml_node_ext_text(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_attr_handler(njs_vm_t *vm, xmlNode *current,
+ njs_str_t *name, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_tag_remove(njs_vm_t *vm, xmlNode *current,
+ njs_str_t *name);
+static njs_int_t njs_xml_node_tag_handler(njs_vm_t *vm, xmlNode *current,
+ njs_str_t *name, njs_value_t *setval, njs_value_t *retval);
+static njs_int_t njs_xml_node_tags_handler(njs_vm_t *vm, xmlNode *current,
+ 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,
+ 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_encode_special_chars(njs_vm_t *vm, njs_str_t *src,
+ njs_str_t *out);
+static njs_int_t njs_xml_replace_node(njs_vm_t *vm, xmlNode *old,
+ xmlNode *current);
+static void njs_xml_node_cleanup(void *data);
+static void njs_xml_doc_cleanup(void *data);
+
static njs_xml_nset_t *njs_xml_nset_create(njs_vm_t *vm, xmlDoc *doc,
xmlNode *current, njs_xml_nset_type_t type);
static njs_xml_nset_t *njs_xml_nset_add(njs_xml_nset_t *nset,
@@ -122,6 +156,29 @@ static njs_external_t njs_ext_xml[] = {
}
},
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("serialize"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_ext_canonicalization,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("serializeToString"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_ext_canonicalization,
+ .magic8 = 3,
+ }
+ },
+
};
@@ -171,12 +228,25 @@ static njs_external_t njs_ext_xml_node[
.flags = NJS_EXTERN_SELF,
.u.object = {
.enumerable = 1,
+ .writable = 1,
+ .configurable = 1,
.prop_handler = njs_xml_node_ext_prop_handler,
.keys = njs_xml_node_ext_prop_keys,
}
},
{
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("addChild"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_node_ext_add_child,
+ }
+ },
+
+ {
.flags = NJS_EXTERN_PROPERTY,
.name.string = njs_str("$attrs"),
.enumerable = 1,
@@ -213,9 +283,77 @@ static njs_external_t njs_ext_xml_node[
},
{
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("removeAllAttributes"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_node_ext_remove_all_attributes,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("removeAttribute"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_node_ext_remove_attribute,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("removeChildren"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_node_ext_remove_children,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("removeText"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_node_ext_remove_text,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("setAttribute"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_node_ext_set_attribute,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("setText"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_xml_node_ext_set_text,
+ }
+ },
+
+ {
.flags = NJS_EXTERN_PROPERTY,
.name.string = njs_str("$tags"),
.enumerable = 1,
+ .writable = 1,
+ .configurable = 1,
.u.property = {
.handler = njs_xml_node_ext_tags,
}
@@ -225,6 +363,7 @@ static njs_external_t njs_ext_xml_node[
.flags = NJS_EXTERN_PROPERTY,
.name.string = njs_str("$text"),
.enumerable = 1,
+ .writable = 1,
.u.property = {
.handler = njs_xml_node_ext_text,
}
@@ -410,16 +549,6 @@ njs_xml_doc_ext_root(njs_vm_t *vm, njs_o
}
-static void
-njs_xml_doc_cleanup(void *data)
-{
- njs_xml_doc_t *current = data;
-
- xmlFreeDoc(current->doc);
- xmlFreeParserCtxt(current->ctx);
-}
-
-
static njs_int_t
njs_xml_node_ext_prop_keys(njs_vm_t *vm, njs_value_t *value, njs_value_t *keys)
{
@@ -515,15 +644,11 @@ njs_xml_node_ext_prop_keys(njs_vm_t *vm,
static njs_int_t
njs_xml_node_ext_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop,
- njs_value_t *value, njs_value_t *unused, njs_value_t *retval)
+ njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
- size_t size;
- xmlAttr *attr;
- xmlNode *node, *current;
- njs_int_t ret;
- njs_str_t name;
- njs_value_t *push;
- const u_char *content;
+ xmlNode *current;
+ njs_int_t ret;
+ njs_str_t name;
/*
* $tag$foo - the first tag child with the name "foo"
@@ -548,108 +673,93 @@ njs_xml_node_ext_prop_handler(njs_vm_t *
if (name.length > njs_length("$attr$")
&& njs_strncmp(&name.start[1], "attr$", njs_length("attr$")) == 0)
{
- for (attr = current->properties; attr != NULL; attr = attr->next) {
- if (attr->type != XML_ATTRIBUTE_NODE) {
- continue;
- }
-
- size = njs_strlen(attr->name);
+ name.length -= njs_length("$attr$");
+ name.start += njs_length("$attr$");
- if (name.length != (size + njs_length("$attr$"))
- || njs_strncmp(&name.start[njs_length("$attr$")],
- attr->name, size) != 0)
- {
- continue;
- }
-
- content = (const u_char *) attr->children->content;
-
- return njs_vm_value_string_create(vm, retval, content,
- njs_strlen(content));
- }
+ return njs_xml_node_attr_handler(vm, current, &name, setval,
+ retval);
}
if (name.length > njs_length("$tag$")
&& njs_strncmp(&name.start[1], "tag$", njs_length("tag$")) == 0)
{
- for (node = current->children; node != NULL; node = node->next) {
- if (node->type != XML_ELEMENT_NODE) {
- continue;
- }
-
- size = njs_strlen(node->name);
+ name.length -= njs_length("$tag$");
+ name.start += njs_length("$tag$");
- if (name.length != (size + njs_length("$tag$"))
- || njs_strncmp(&name.start[njs_length("$tag$")],
- node->name, size) != 0)
- {
- continue;
- }
-
- return njs_vm_external_create(vm, retval, njs_xml_node_proto_id,
- node, 0);
- }
+ return njs_xml_node_tag_handler(vm, current, &name, setval, retval);
}
if (name.length >= njs_length("$tags$")
&& njs_strncmp(&name.start[1], "tags$", njs_length("tags$")) == 0)
{
- ret = njs_vm_array_alloc(vm, retval, 2);
- if (njs_slow_path(ret != NJS_OK)) {
- return NJS_ERROR;
- }
-
- for (node = current->children; node != NULL; node = node->next) {
- if (node->type != XML_ELEMENT_NODE) {
- continue;
- }
-
- size = njs_strlen(node->name);
+ name.length -= njs_length("$tags$");
+ name.start += njs_length("$tags$");
- if (name.length > njs_length("$tags$")
- && (name.length != (size + njs_length("$tags$"))
- || njs_strncmp(&name.start[njs_length("$tags$")],
- node->name, size) != 0))
- {
- continue;
- }
-
- push = njs_vm_array_push(vm, retval);
- if (njs_slow_path(push == NULL)) {
- return NJS_ERROR;
- }
-
- ret = njs_vm_external_create(vm, push, njs_xml_node_proto_id,
- node, 0);
- if (njs_slow_path(ret != NJS_OK)) {
- return NJS_ERROR;
- }
- }
-
- return NJS_OK;
+ return njs_xml_node_tags_handler(vm, current, &name, setval,
+ retval);
}
}
- for (node = current->children; node != NULL; node = node->next) {
- if (node->type != XML_ELEMENT_NODE) {
- continue;
- }
+ return njs_xml_node_tag_handler(vm, current, &name, setval, retval);
+}
+
- size = njs_strlen(node->name);
+static njs_int_t
+njs_xml_node_ext_add_child(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused)
+{
+ xmlNode *current, *node, *copy;
+ njs_int_t ret;
- if (name.length != size
- || njs_strncmp(name.start, node->name, size) != 0)
- {
- continue;
- }
+ current = njs_vm_external(vm, njs_xml_node_proto_id, njs_argument(args, 0));
+ if (njs_slow_path(current == NULL)) {
+ njs_vm_error(vm, "\"this\" is not a XMLNode object");
+ return NJS_ERROR;
+ }
- return njs_vm_external_create(vm, retval, njs_xml_node_proto_id,
- node, 0);
+ copy = xmlDocCopyNode(current, current->doc, 1);
+ if (njs_slow_path(copy == NULL)) {
+ njs_vm_error(vm, "xmlDocCopyNode() failed");
+ return NJS_ERROR;
+ }
+
+ node = njs_xml_external_node(vm, njs_arg(args, nargs, 1));
+ if (njs_slow_path(node == NULL)) {
+ njs_vm_error(vm, "node is not a XMLNode object");
+ goto error;
}
- njs_value_undefined_set(retval);
+ node = xmlDocCopyNode(node, current->doc, 1);
+ if (njs_slow_path(node == NULL)) {
+ njs_vm_error(vm, "xmlDocCopyNode() failed");
+ goto error;
+ }
+
+ node = xmlAddChild(copy, node);
+ if (njs_slow_path(node == NULL)) {
+ njs_vm_error(vm, "xmlAddChild() failed");
+ goto error;
+ }
- return NJS_DECLINED;
+ ret = xmlReconciliateNs(current->doc, copy);
+ if (njs_slow_path(ret == -1)) {
+ njs_vm_error(vm, "xmlReconciliateNs() failed");
+ return NJS_ERROR;
+ }
+
+ njs_value_undefined_set(njs_vm_retval(vm));
+
+ return njs_xml_replace_node(vm, current, copy);
+
+error:
+
+ if (node != NULL) {
+ xmlFreeNode(node);
+ }
+
+ xmlFreeNode(copy);
+
+ return NJS_ERROR;
}
@@ -725,12 +835,133 @@ njs_xml_node_ext_parent(njs_vm_t *vm, nj
static njs_int_t
+njs_xml_node_ext_remove_attribute(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused)
+{
+ return njs_xml_node_ext_set_attribute(vm, args, nargs, 1);
+}
+
+
+static njs_int_t
+njs_xml_node_ext_remove_all_attributes(njs_vm_t *vm,
+ njs_value_t *args, njs_uint_t nargs, njs_index_t unused)
+{
+ xmlNode *current;
+
+ current = njs_vm_external(vm, njs_xml_node_proto_id, njs_argument(args, 0));
+ if (njs_slow_path(current == NULL)) {
+ njs_vm_error(vm, "\"this\" is not a XMLNode object");
+ return NJS_ERROR;
+ }
+
+ if (current->properties != NULL) {
+ xmlFreePropList(current->properties);
+ current->properties = NULL;
+ }
+
+ njs_value_undefined_set(njs_vm_retval(vm));
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
+njs_xml_node_ext_remove_children(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused)
+{
+ xmlNode *current, *copy;
+ 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)) {
+ njs_vm_error(vm, "\"this\" is not a XMLNode object");
+ return NJS_ERROR;
+ }
+
+ selector = njs_arg(args, nargs, 1);
+
+ njs_value_undefined_set(njs_vm_retval(vm));
+
+ if (!njs_value_is_null_or_undefined(selector)) {
+ if (njs_slow_path(!njs_value_is_string(selector))) {
+ njs_vm_error(vm, "selector is not a string");
+ return NJS_ERROR;
+ }
+
+ njs_value_string_get(selector, &name);
+
+ return njs_xml_node_tag_remove(vm, current, &name);
+ }
+
+ /* all. */
+
+ copy = xmlDocCopyNode(current, current->doc, 1);
+ if (njs_slow_path(copy == NULL)) {
+ njs_vm_error(vm, "xmlDocCopyNode() failed");
+ return NJS_ERROR;
+ }
+
+ if (copy->children != NULL) {
+ xmlFreeNodeList(copy->children);
+ copy->children = NULL;
+ }
+
+ return njs_xml_replace_node(vm, current, copy);
+}
+
+
+static njs_int_t
+njs_xml_node_ext_remove_text(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused)
+{
+ return njs_xml_node_ext_text(vm, NULL, njs_argument(args, 0), NULL, NULL);
+}
+
+
+static njs_int_t
+njs_xml_node_ext_set_attribute(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t remove)
+{
+ xmlNode *current;
+ njs_str_t str;
+ njs_value_t *name;
+
+ current = njs_vm_external(vm, njs_xml_node_proto_id, njs_argument(args, 0));
+ if (njs_slow_path(current == NULL)) {
+ njs_vm_error(vm, "\"this\" is not a XMLNode object");
+ return NJS_ERROR;
+ }
+
+ name = njs_arg(args, nargs, 1);
+
+ if (njs_slow_path(!njs_value_is_string(name))) {
+ njs_vm_error(vm, "name is not a string");
+ return NJS_ERROR;
+ }
+
+ njs_value_string_get(name, &str);
+
+ return njs_xml_node_attr_handler(vm, current, &str, njs_arg(args, nargs, 2),
+ !remove ? njs_vm_retval(vm) : NULL);
+}
+
+
+static njs_int_t
+njs_xml_node_ext_set_text(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t unused)
+{
+ return njs_xml_node_ext_text(vm, NULL, njs_argument(args, 0),
+ njs_arg(args, nargs, 1), njs_vm_retval(vm));
+}
+
+
+static njs_int_t
njs_xml_node_ext_tags(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value,
njs_value_t *setval, njs_value_t *retval)
{
- xmlNode *node, *current;
- njs_int_t ret;
- njs_value_t *push;
+ xmlNode *current;
+ njs_str_t name;
current = njs_vm_external(vm, njs_xml_node_proto_id, value);
if (njs_slow_path(current == NULL || current->children == NULL)) {
@@ -738,38 +969,21 @@ njs_xml_node_ext_tags(njs_vm_t *vm, njs_
return NJS_DECLINED;
}
- ret = njs_vm_array_alloc(vm, retval, 2);
- if (njs_slow_path(ret != NJS_OK)) {
- return NJS_ERROR;
- }
-
- for (node = current->children; node != NULL; node = node->next) {
- if (node->type != XML_ELEMENT_NODE) {
- continue;
- }
+ name.start = NULL;
+ name.length = 0;
- push = njs_vm_array_push(vm, retval);
- if (njs_slow_path(push == NULL)) {
- return NJS_ERROR;
- }
-
- ret = njs_vm_external_create(vm, push, njs_xml_node_proto_id, node, 0);
- if (njs_slow_path(ret != NJS_OK)) {
- return NJS_ERROR;
- }
- }
-
- return NJS_OK;
+ return njs_xml_node_tags_handler(vm, current, &name, setval, retval);
}
static njs_int_t
-njs_xml_node_ext_text(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value,
- njs_value_t *setval, njs_value_t *retval)
+njs_xml_node_ext_text(njs_vm_t *vm, njs_object_prop_t *unused,
+ njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
- xmlNode *current, *node;
+ u_char *text;
+ xmlNode *current, *copy;
njs_int_t ret;
- njs_chb_t chain;
+ njs_str_t content, enc;
current = njs_vm_external(vm, njs_xml_node_proto_id, value);
if (njs_slow_path(current == NULL)) {
@@ -777,21 +991,551 @@ njs_xml_node_ext_text(njs_vm_t *vm, njs_
return NJS_DECLINED;
}
- njs_chb_init(&chain, njs_vm_memory_pool(vm));
+ if (retval != NULL && setval == NULL) {
+ text = xmlNodeGetContent(current);
+ ret = njs_vm_value_string_create(vm, retval, text, njs_strlen(text));
+
+ xmlFree(text);
+
+ return ret;
+ }
+
+ /* set or delete. */
+
+ enc.start = NULL;
+ enc.length = 0;
+
+ if (retval != NULL
+ && (setval != NULL && !njs_value_is_null_or_undefined(setval)))
+ {
+ if (njs_slow_path(!njs_value_is_string(setval))) {
+ njs_vm_error(vm, "setval is not a string");
+ return NJS_ERROR;
+ }
+
+ njs_value_string_get(setval, &content);
+
+ ret = njs_xml_encode_special_chars(vm, &content, &enc);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+
+ copy = xmlDocCopyNode(current, current->doc, 1);
+ if (njs_slow_path(copy == NULL)) {
+ njs_vm_error(vm, "xmlDocCopyNode() failed");
+ return NJS_ERROR;
+ }
+
+ xmlNodeSetContentLen(copy, enc.start, enc.length);
+
+ if (retval != NULL) {
+ njs_value_undefined_set(retval);
+ }
+
+ return njs_xml_replace_node(vm, current, copy);
+}
+
+
+static njs_int_t
+njs_xml_node_attr_handler(njs_vm_t *vm, xmlNode *current, njs_str_t *name,
+ njs_value_t *setval, njs_value_t *retval)
+{
+ size_t size;
+ njs_int_t ret;
+ xmlAttr *attr;
+ const u_char *content, *value;
+ u_char name_buf[512], value_buf[1024];
+
+ if (retval != NULL && setval == NULL) {
+ /* get. */
+
+ for (attr = current->properties; attr != NULL; attr = attr->next) {
+ if (attr->type != XML_ATTRIBUTE_NODE) {
+ continue;
+ }
+
+ size = njs_strlen(attr->name);
+
+ if (name->length != size
+ || njs_strncmp(name->start, attr->name, size) != 0)
+ {
+ continue;
+ }
+
+ if (attr->children != NULL
+ && attr->children->next == NULL
+ && attr->children->type == XML_TEXT_NODE)
+ {
+ content = (const u_char *) attr->children->content;
- for (node = current->children; node != NULL; node = node->next) {
- if (node->type != XML_TEXT_NODE) {
+ return njs_vm_value_string_create(vm, retval, content,
+ njs_strlen(content));
+ }
+ }
+
+ njs_value_undefined_set(retval);
+
+ return NJS_DECLINED;
+ }
+
+ /* 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)) {
+ return NJS_ERROR;
+ }
+
+ ret = xmlValidateQName(&name_buf[0], 0);
+ if (njs_slow_path(ret != 0)) {
+ njs_vm_error(vm, "attribute name \"%V\" is not valid", name);
+ return NJS_ERROR;
+ }
+
+ if (retval == NULL
+ || (setval != NULL && njs_value_is_null_or_undefined(setval)))
+ {
+ /* delete. */
+
+ attr = xmlHasProp(current, &name_buf[0]);
+
+ 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(value == NULL)) {
+ return NJS_ERROR;
+ }
+
+ attr = xmlSetProp(current, &name_buf[0], value);
+ if (njs_slow_path(attr == NULL)) {
+ njs_vm_error(vm, "xmlSetProp() failed");
+ return NJS_ERROR;
+ }
+
+ njs_value_undefined_set(retval);
+
+ return NJS_OK;
+}
+
+
+static njs_int_t
+njs_xml_node_tag_remove(njs_vm_t *vm, xmlNode *current, njs_str_t *name)
+{
+ size_t size;
+ xmlNode *node, *next, *copy;
+ njs_int_t ret;
+
+ copy = xmlDocCopyNode(current, current->doc, 1);
+ if (njs_slow_path(copy == NULL)) {
+ njs_vm_error(vm, "xmlDocCopyNode() failed");
+ 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)
+ {
continue;
}
- njs_chb_append(&chain, node->content, njs_strlen(node->content));
+ ret = njs_xml_replace_node(vm, node, NULL);
+ if (njs_slow_path(ret != NJS_OK)) {
+ xmlFreeNode(copy);
+ return NJS_ERROR;
+ }
+ }
+
+ return njs_xml_replace_node(vm, current, copy);
+}
+
+
+static njs_int_t
+njs_xml_node_tag_handler(njs_vm_t *vm, xmlNode *current, njs_str_t *name,
+ njs_value_t *setval, njs_value_t *retval)
+{
+ size_t size;
+ xmlNode *node;
+
+ if (retval != NULL && setval == NULL) {
+
+ /* get. */
+
+ for (node = current->children; node != NULL; node = 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)
+ {
+ continue;
+ }
+
+ return njs_vm_external_create(vm, retval, njs_xml_node_proto_id,
+ node, 0);
+ }
+
+ njs_value_undefined_set(retval);
+
+ return NJS_DECLINED;
+ }
+
+ if (retval != NULL) {
+ njs_vm_error(vm, "XMLNode.$tag$xxx is not assignable, use addChild() or"
+ "node.$tags = [node1, node2, ..] syntax");
+ return NJS_ERROR;
+ }
+
+ /* delete. */
+
+ return njs_xml_node_tag_remove(vm, current, name);
+}
+
+
+static njs_int_t
+njs_xml_node_tags_handler(njs_vm_t *vm, xmlNode *current, njs_str_t *name,
+ njs_value_t *setval, njs_value_t *retval)
+{
+ size_t size;
+ int64_t i, length;
+ xmlNode *node, *copy;
+ njs_int_t ret;
+ njs_value_t *push;
+ njs_opaque_value_t *start;
+
+ if (retval != NULL && setval == NULL) {
+
+ /* get. */
+
+ ret = njs_vm_array_alloc(vm, retval, 2);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ for (node = current->children; node != NULL; node = node->next) {
+ if (node->type != XML_ELEMENT_NODE) {
+ continue;
+ }
+
+ size = njs_strlen(node->name);
+
+ if (name->length > 0
+ && (name->length != size
+ || njs_strncmp(name->start, node->name, size) != 0))
+ {
+ continue;
+ }
+
+ push = njs_vm_array_push(vm, retval);
+ if (njs_slow_path(push == NULL)) {
+ return NJS_ERROR;
+ }
+
+ ret = njs_vm_external_create(vm, push, njs_xml_node_proto_id,
+ node, 0);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+
+ return NJS_OK;
+ }
+
+ if (name->length > 0) {
+ njs_vm_error(vm, "XMLNode $tags$xxx is not assignable, use addChild() "
+ "or node.$tags = [node1, node2, ..] syntax");
+ return NJS_ERROR;
+ }
+
+ /* set or delete. */
+
+ copy = xmlDocCopyNode(current, current->doc, 1);
+ if (njs_slow_path(copy == NULL)) {
+ njs_vm_error(vm, "xmlDocCopyNode() failed");
+ return NJS_ERROR;
+ }
+
+ if (copy->children != NULL) {
+ xmlFreeNodeList(copy->children);
+ copy->children = NULL;
+ }
+
+ if (retval == NULL) {
+ /* delete. */
+ return njs_xml_replace_node(vm, current, copy);
+ }
+
+ if (!njs_value_is_array(setval)) {
+ njs_vm_error(vm, "setval is not an array");
+ goto error;
+ }
+
+ start = (njs_opaque_value_t *) njs_vm_array_start(vm, setval);
+ if (njs_slow_path(start == NULL)) {
+ goto error;
+ }
+
+ (void) njs_vm_array_length(vm, setval, &length);
+
+ for (i = 0; i < length; i++) {
+ node = njs_xml_external_node(vm, njs_value_arg(start++));
+ if (njs_slow_path(node == NULL)) {
+ njs_vm_error(vm, "setval[%D] is not a XMLNode object", i);
+ goto error;
+ }
+
+ node = xmlDocCopyNode(node, current->doc, 1);
+ if (njs_slow_path(node == NULL)) {
+ njs_vm_error(vm, "xmlDocCopyNode() failed");
+ goto error;
+ }
+
+ node = xmlAddChild(copy, node);
+ if (njs_slow_path(node == NULL)) {
+ njs_vm_error(vm, "xmlAddChild() failed");
+ xmlFreeNode(node);
+ goto error;
+ }
+
+ ret = xmlReconciliateNs(current->doc, copy);
+ if (njs_slow_path(ret == -1)) {
+ njs_vm_error(vm, "xmlReconciliateNs() failed");
+ return NJS_ERROR;
+ }
}
- ret = njs_vm_value_string_create_chb(vm, retval, &chain);
+ njs_value_undefined_set(retval);
+
+ return njs_xml_replace_node(vm, current, copy);
+
+error:
+
+ xmlFreeNode(copy);
+
+ return NJS_ERROR;
+}
+
+
+static xmlNode *
+njs_xml_external_node(njs_vm_t *vm, njs_value_t *value)
+{
+ xmlNode *current;
+ njs_xml_doc_t *tree;
+
+ current = njs_vm_external(vm, njs_xml_node_proto_id, value);
+ if (njs_slow_path(current == NULL)) {
+ tree = njs_vm_external(vm, njs_xml_doc_proto_id, value);
+ if (njs_slow_path(tree == NULL)) {
+ njs_vm_error(vm, "\"this\" is not a XMLNode object");
+ return NULL;
+ }
+
+ current = xmlDocGetRootElement(tree->doc);
+ if (njs_slow_path(current == NULL)) {
+ njs_vm_error(vm, "\"this\" is not a XMLNode object");
+ return NULL;
+ }
+ }
+
+ return current;
+}
+
+
+static njs_int_t
+njs_xml_str_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_error(vm, "njs_xml_str_to_c_string() very long string, "
+ "length >= %uz", size - njs_length("\0"));
+ return NJS_ERROR;
+ }
+
+ p = njs_cpymem(dst, str->start, str->length);
+ *p = '\0';
+
+ return NJS_OK;
+}
More information about the nginx-devel
mailing list