details: https://hg.nginx.org/njs/rev/3891f338e2c9 branches: changeset: 2049:3891f338e2c9 user: Dmitry Volyntsev <xei...@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; +} _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel