/* * Copyright (C) Dmitry Volyntsev * Copyright (C) F5, Inc. */ #include #include #include #include #include #include typedef struct { xmlDoc *doc; xmlParserCtxt *ctx; xmlNode *free; int ref_count; } qjs_xml_doc_t; typedef struct { xmlNode *node; qjs_xml_doc_t *doc; } qjs_xml_node_t; typedef struct { xmlAttr *attr; qjs_xml_doc_t *doc; } qjs_xml_attr_t; typedef enum { XML_NSET_TREE = 0, XML_NSET_TREE_NO_COMMENTS, XML_NSET_TREE_INVERT, } qjs_xml_nset_type_t; typedef struct qjs_xml_nset_s qjs_xml_nset_t; struct qjs_xml_nset_s { xmlNodeSet *nodes; xmlDoc *doc; qjs_xml_nset_type_t type; qjs_xml_nset_t *next; qjs_xml_nset_t *prev; }; static JSValue qjs_xml_parse(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_xml_canonicalization(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); static int qjs_xml_doc_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); static int qjs_xml_doc_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); static void qjs_xml_doc_finalizer(JSRuntime *rt, JSValue val); static void qjs_xml_doc_free(JSRuntime *rt, qjs_xml_doc_t *current); static JSValue qjs_xml_node_make(JSContext *cx, qjs_xml_doc_t *doc, xmlNode *node); static int qjs_xml_node_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); static int qjs_xml_node_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); static int qjs_xml_node_set_property(JSContext *cx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); static int qjs_xml_node_delete_property(JSContext *cx, JSValueConst obj, JSAtom prop); static int qjs_xml_node_define_own_property(JSContext *cx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst getter, JSValueConst setter, int flags); static JSValue qjs_xml_node_add_child(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_xml_node_remove_children(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_xml_node_set_attribute(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_xml_node_remove_attribute(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_xml_node_remove_all_attributes(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_xml_node_set_text(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_xml_node_remove_text(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); static void qjs_xml_node_finalizer(JSRuntime *rt, JSValue val); static xmlNode *qjs_xml_node(JSContext *cx, JSValueConst val, xmlDoc **doc); static JSValue qjs_xml_attr_make(JSContext *cx, qjs_xml_doc_t *doc, xmlAttr *attr); static int qjs_xml_attr_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop); static int qjs_xml_attr_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj); static void qjs_xml_attr_finalizer(JSRuntime *rt, JSValue val); static u_char **qjs_xml_parse_ns_list(JSContext *cx, u_char *src); static int qjs_xml_c14n_visibility_cb(void *user_data, xmlNode *node, xmlNode *parent); static int qjs_xml_buf_write_cb(void *context, const char *buffer, int len); static qjs_xml_nset_t *qjs_xml_nset_create(JSContext *cx, xmlDoc *doc, xmlNode *current, qjs_xml_nset_type_t type); 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 int qjs_xml_encode_special_chars(JSContext *cx, njs_str_t *src, njs_str_t *out); static void qjs_xml_replace_node(JSContext *cx, qjs_xml_node_t *node, xmlNode *current); static void qjs_xml_error(JSContext *cx, qjs_xml_doc_t *current, const char *fmt, ...); static JSModuleDef *qjs_xml_init(JSContext *ctx, const char *name); static const JSCFunctionListEntry qjs_xml_export[] = { JS_CFUNC_DEF("parse", 2, qjs_xml_parse), JS_CFUNC_MAGIC_DEF("c14n", 4, qjs_xml_canonicalization, 0), JS_CFUNC_MAGIC_DEF("exclusiveC14n", 4, qjs_xml_canonicalization, 1), JS_CFUNC_MAGIC_DEF("serialize", 4, qjs_xml_canonicalization, 0), JS_CFUNC_MAGIC_DEF("serializeToString", 4, qjs_xml_canonicalization, 2), }; static const JSCFunctionListEntry qjs_xml_doc_proto[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "XMLDoc", JS_PROP_CONFIGURABLE), }; static const JSCFunctionListEntry qjs_xml_node_proto[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "XMLNode", JS_PROP_CONFIGURABLE), JS_CFUNC_DEF("addChild", 1, qjs_xml_node_add_child), JS_CFUNC_DEF("removeChildren", 1, qjs_xml_node_remove_children), JS_CFUNC_DEF("setAttribute", 2, qjs_xml_node_set_attribute), JS_CFUNC_DEF("removeAttribute", 1, qjs_xml_node_remove_attribute), JS_CFUNC_DEF("removeAllAttributes", 0, qjs_xml_node_remove_all_attributes), JS_CFUNC_DEF("setText", 1, qjs_xml_node_set_text), JS_CFUNC_DEF("removeText", 0, qjs_xml_node_remove_text), }; static const JSCFunctionListEntry qjs_xml_attr_proto[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "XMLAttr", JS_PROP_CONFIGURABLE), }; static JSClassDef qjs_xml_doc_class = { "XMLDoc", .finalizer = qjs_xml_doc_finalizer, .exotic = & (JSClassExoticMethods) { .get_own_property = qjs_xml_doc_get_own_property, .get_own_property_names = qjs_xml_doc_get_own_property_names, }, }; static JSClassDef qjs_xml_node_class = { "XMLNode", .finalizer = qjs_xml_node_finalizer, .exotic = & (JSClassExoticMethods) { .get_own_property = qjs_xml_node_get_own_property, .get_own_property_names = qjs_xml_node_get_own_property_names, .set_property = qjs_xml_node_set_property, .delete_property = qjs_xml_node_delete_property, .define_own_property = qjs_xml_node_define_own_property, }, }; static JSClassDef qjs_xml_attr_class = { "XMLAttr", .finalizer = qjs_xml_attr_finalizer, .exotic = & (JSClassExoticMethods) { .get_own_property = qjs_xml_attr_get_own_property, .get_own_property_names = qjs_xml_attr_get_own_property_names, }, }; qjs_module_t qjs_xml_module = { .name = "xml", .init = qjs_xml_init, }; static JSValue qjs_xml_parse(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValue ret; qjs_bytes_t data; qjs_xml_doc_t *tree; if (qjs_to_bytes(cx, &data, argv[0]) < 0) { return JS_EXCEPTION; } tree = js_mallocz(cx, sizeof(qjs_xml_doc_t)); if (tree == NULL) { qjs_bytes_free(cx, &data); JS_ThrowOutOfMemory(cx); return JS_EXCEPTION; } tree->ref_count = 1; tree->ctx = xmlNewParserCtxt(); if (tree->ctx == NULL) { qjs_bytes_free(cx, &data); JS_ThrowInternalError(cx, "xmlNewParserCtxt() failed"); qjs_xml_doc_free(JS_GetRuntime(cx), tree); return JS_EXCEPTION; } tree->doc = xmlCtxtReadMemory(tree->ctx, (char *) data.start, data.length, NULL, NULL, XML_PARSE_NOWARNING | XML_PARSE_NOERROR); qjs_bytes_free(cx, &data); if (tree->doc == NULL) { qjs_xml_error(cx, tree, "failed to parse XML"); qjs_xml_doc_free(JS_GetRuntime(cx), tree); return JS_EXCEPTION; } ret = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_XML_DOC); if (JS_IsException(ret)) { qjs_xml_doc_free(JS_GetRuntime(cx), tree); return ret; } JS_SetOpaque(ret, tree); return ret; } static JSValue qjs_xml_canonicalization(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { int comments; u_char **prefix_list, *pref; xmlDoc *doc; xmlNode *node; ssize_t size; JSValue excluding, prefixes, ret; njs_chb_t chain; qjs_xml_node_t *nd; qjs_xml_nset_t *nset, *children; xmlOutputBuffer *buf; node = qjs_xml_node(cx, argv[0], &doc); if (node == NULL) { return JS_EXCEPTION; } comments = JS_ToBool(cx, argv[2]); if (comments < 0) { return JS_EXCEPTION; } buf = NULL; nset = NULL; children = NULL; excluding = argv[1]; if (!JS_IsNullOrUndefined(excluding)) { nd = JS_GetOpaque(excluding, QJS_CORE_CLASS_ID_XML_NODE); if (nd == NULL) { JS_ThrowTypeError(cx, "\"excluding\" argument is not a XMLNode " "object"); return JS_EXCEPTION; } nset = qjs_xml_nset_create(cx, doc, node, XML_NSET_TREE_NO_COMMENTS); if (nset == NULL) { return JS_ThrowOutOfMemory(cx); } children = qjs_xml_nset_create(cx, nd->doc->doc, nd->node, XML_NSET_TREE_INVERT); if (children == NULL) { qjs_xml_nset_free(cx, nset); return JS_ThrowOutOfMemory(cx); } nset = qjs_xml_nset_add(nset, children); } else { nset = qjs_xml_nset_create(cx, doc, node, comments ? XML_NSET_TREE : XML_NSET_TREE_NO_COMMENTS); if (nset == NULL) { return JS_ThrowOutOfMemory(cx); } } prefix_list = NULL; prefixes = argv[3]; if (!JS_IsNullOrUndefined(prefixes)) { if (!JS_IsString(prefixes)) { JS_ThrowTypeError(cx, "\"prefixes\" argument is not a string"); goto fail; } pref = (u_char *) JS_ToCString(cx, prefixes); if (pref == NULL) { JS_ThrowOutOfMemory(cx); goto fail; } prefix_list = qjs_xml_parse_ns_list(cx, pref); if (prefix_list == NULL) { goto fail; } } NJS_CHB_CTX_INIT(&chain, cx); buf = xmlOutputBufferCreateIO(qjs_xml_buf_write_cb, NULL, &chain, NULL); if (buf == NULL) { JS_ThrowInternalError(cx, "xmlOutputBufferCreateIO() failed"); goto fail; } size = xmlC14NExecute(doc, qjs_xml_c14n_visibility_cb, nset, magic & 0x1 ? XML_C14N_EXCLUSIVE_1_0 : XML_C14N_1_0, prefix_list, comments, buf); if (size < 0) { njs_chb_destroy(&chain); (void) xmlOutputBufferClose(buf); JS_ThrowInternalError(cx, "xmlC14NExecute() failed"); goto fail; } if (magic & 0x2) { ret = qjs_string_create_chb(cx, &chain); } else { ret = qjs_buffer_chb_alloc(cx, &chain); njs_chb_destroy(&chain); } (void) xmlOutputBufferClose(buf); qjs_xml_nset_free(cx, nset); qjs_xml_nset_free(cx, children); if (prefix_list != NULL) { js_free(cx, prefix_list); } return ret; fail: qjs_xml_nset_free(cx, nset); qjs_xml_nset_free(cx, children); if (prefix_list != NULL) { js_free(cx, prefix_list); } return JS_EXCEPTION; } static int qjs_xml_doc_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) { int any; xmlNode *node; njs_str_t name; qjs_xml_doc_t *tree; tree = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_DOC); if (tree == NULL) { (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLDoc"); return -1; } name.start = (u_char *) JS_AtomToCString(cx, prop); if (name.start == NULL) { return -1; } name.length = njs_strlen(name.start); any = (name.length == njs_strlen("$root") && njs_strncmp(name.start, "$root", name.length) == 0); for (node = xmlDocGetRootElement(tree->doc); node != NULL; node = node->next) { if (node->type != XML_ELEMENT_NODE) { continue; } if (!any) { if (name.length != njs_strlen(node->name) || njs_strncmp(name.start, node->name, name.length) != 0) { continue; } } JS_FreeCString(cx, (char *) name.start); if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = qjs_xml_node_make(cx, tree, node); if (JS_IsException(pdesc->value)) { return -1; } } return 1; } JS_FreeCString(cx, (char *) name.start); return 0; } static int qjs_xml_push_string(JSContext *cx, JSValue obj, const char *start) { JSAtom key; key = JS_NewAtomLen(cx, start, njs_strlen(start)); if (key == JS_ATOM_NULL) { return -1; } if (JS_DefinePropertyValue(cx, obj, key, JS_UNDEFINED, JS_PROP_ENUMERABLE) < 0) { JS_FreeAtom(cx, key); return -1; } JS_FreeAtom(cx, key); return 0; } static int qjs_xml_doc_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) { int rc; JSValue keys; xmlNode *node; qjs_xml_doc_t *tree; tree = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_DOC); if (tree == NULL) { (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLDoc"); return -1; } keys = JS_NewObject(cx); if (JS_IsException(keys)) { return -1; } for (node = xmlDocGetRootElement(tree->doc); node != NULL; node = node->next) { if (node->type != XML_ELEMENT_NODE) { continue; } if (qjs_xml_push_string(cx, keys, (char *) node->name) < 0) { JS_FreeValue(cx, keys); return -1; } } rc = JS_GetOwnPropertyNames(cx, ptab, plen, keys, JS_GPN_STRING_MASK); JS_FreeValue(cx, keys); return rc; } static void qjs_xml_doc_free(JSRuntime *rt, qjs_xml_doc_t *current) { xmlNode *node, *next; if (--current->ref_count > 0) { return; } node = current->free; while (node != NULL) { next = node->next; xmlFreeNode(node); node = next; } if (current->doc != NULL) { xmlFreeDoc(current->doc); } if (current->ctx != NULL) { xmlFreeParserCtxt(current->ctx); } js_free_rt(rt, current); } static void qjs_xml_doc_finalizer(JSRuntime *rt, JSValue val) { qjs_xml_doc_t *current; current = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_DOC); qjs_xml_doc_free(rt, current); } static JSValue qjs_xml_node_make(JSContext *cx, qjs_xml_doc_t *doc, xmlNode *node) { JSValue ret; qjs_xml_node_t *current; current = js_malloc(cx, sizeof(qjs_xml_node_t)); if (current == NULL) { JS_ThrowOutOfMemory(cx); return JS_EXCEPTION; } current->node = node; current->doc = doc; doc->ref_count++; ret = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_XML_NODE); if (JS_IsException(ret)) { js_free(cx, current); return ret; } JS_SetOpaque(ret, current); return ret; } static JSValue qjs_xml_node_tag_handler(JSContext *cx, qjs_xml_node_t *current, njs_str_t *name) { size_t size; xmlNode *node; node = current->node; for (node = node->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; } return qjs_xml_node_make(cx, current->doc, node); } return JS_UNDEFINED; } static JSValue qjs_xml_node_tags_handler(JSContext *cx, qjs_xml_node_t *current, njs_str_t *name) { size_t size; int32_t i; xmlNode *node; JSValue arr, ret; arr = JS_NewArray(cx); if (JS_IsException(arr)) { return arr; } i = 0; node = current->node; for (node = node->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; } ret = qjs_xml_node_make(cx, current->doc, node); if (JS_IsException(ret)) { JS_FreeValue(cx, arr); return JS_EXCEPTION; } if (JS_SetPropertyUint32(cx, arr, i++, ret) < 0) { JS_FreeValue(cx, arr); JS_FreeValue(cx, ret); return JS_EXCEPTION; } } return arr; } static JSValue qjs_xml_node_attr_handler(JSContext *cx, qjs_xml_node_t *current, njs_str_t *name) { size_t size; xmlAttr *attr; xmlNode *node; node = current->node; for (attr = node->properties; attr != NULL; attr = attr->next) { if (attr->type != XML_ATTRIBUTE_NODE) { continue; } size = njs_strlen(attr->name); if (name->length > 0 && (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 && attr->children->content != NULL) { return JS_NewString(cx, (char *) attr->children->content); } } return JS_UNDEFINED; } static int qjs_xml_node_attr_modify(JSContext *cx, JSValue current, const u_char *name, JSValue setval) { xmlAttr *attr; const u_char *value; qjs_xml_node_t *node; node = JS_GetOpaque(current, QJS_CORE_CLASS_ID_XML_NODE); if (node == NULL) { return -1; } if (xmlValidateQName(name, 0) != 0) { JS_ThrowTypeError(cx, "attribute name \"%s\" is not valid", name); return -1; } if (JS_IsNullOrUndefined(setval)) { attr = xmlHasProp(node->node, name); if (attr != NULL) { xmlRemoveProp(attr); } return 1; } value = (const u_char *) JS_ToCString(cx, setval); if (value == NULL) { return -1; } attr = xmlSetProp(node->node, name, value); JS_FreeCString(cx, (char *) value); if (attr == NULL) { JS_ThrowInternalError(cx, "xmlSetProp() failed"); return -1; } return 1; } static int qjs_xml_node_tag_modify(JSContext *cx, JSValue obj, njs_str_t *name, JSValue setval) { size_t size; xmlNode *node, *next, *copy; qjs_xml_node_t *current; current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE); if (current == NULL) { return -1; } if (!JS_IsNullOrUndefined(setval)) { JS_ThrowInternalError(cx, "XMLNode.$tag$xxx is not assignable, " "use addChild() or node.$tags = [node1, node2, ..] " "syntax"); 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; } size = njs_strlen(node->name); if (name->length > 0 && (name->length != size || njs_strncmp(name->start, node->name, size) != 0)) { continue; } xmlUnlinkNode(node); node->next = current->doc->free; current->doc->free = node; } qjs_xml_replace_node(cx, current, copy); return 1; } static int 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; qjs_xml_node_t *current; current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE); if (current == NULL) { return -1; } if (!qjs_is_array(cx, setval)) { JS_ThrowTypeError(cx, "setval is not an array"); return -1; } length = JS_GetPropertyStr(cx, setval, "length"); if (JS_IsException(length)) { return -1; } if (JS_ToInt32(cx, &len, length) < 0) { return -1; } copy = xmlDocCopyNode(current->node, current->doc->doc, 2 /* copy properties and namespaces */); if (copy == NULL) { JS_ThrowInternalError(cx, "xmlDocCopyNode() failed"); return -1; } for (i = 0; i < len; i++) { v = JS_GetPropertyUint32(cx, setval, i); if (JS_IsException(v)) { goto error; } node = qjs_xml_node(cx, v, NULL); JS_FreeValue(cx, v); if (node == NULL) { 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; } } if (xmlReconciliateNs(current->doc->doc, copy) == -1) { JS_ThrowInternalError(cx, "xmlReconciliateNs() failed"); goto error; } qjs_xml_replace_node(cx, current, copy); return 1; error: xmlFreeNode(copy); return -1; } static int qjs_xml_node_text_handler(JSContext *cx, JSValue current, JSValue setval) { xmlNode *copy; njs_str_t content, enc; qjs_xml_node_t *node; enc.start = NULL; enc.length = 0; node = JS_GetOpaque(current, QJS_CORE_CLASS_ID_XML_NODE); if (node == NULL) { return -1; } if (!JS_IsNullOrUndefined(setval)) { content.start = (u_char *) JS_ToCStringLen(cx, &content.length, setval); if (content.start == NULL) { return -1; } if (qjs_xml_encode_special_chars(cx, &content, &enc) < 0) { JS_FreeCString(cx, (char *) content.start); return -1; } JS_FreeCString(cx, (char *) content.start); } copy = xmlDocCopyNode(node->node, node->doc->doc, 1); if (copy == NULL) { if (enc.start != NULL) { js_free(cx, enc.start); } JS_ThrowInternalError(cx, "xmlDocCopyNode() failed"); return -1; } xmlNodeSetContentLen(copy, enc.start, enc.length); if (enc.start != NULL) { js_free(cx, enc.start); } qjs_xml_replace_node(cx, node, copy); return 1; } static int qjs_xml_node_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) { u_char *text; JSValue value; xmlNode *node; njs_str_t name, nm; qjs_xml_node_t *current; /* * $tag$foo - the first tag child with the name "foo" * $tags$foo - the all children with the name "foo" as an array * $attr$foo - the attribute with the name "foo" * foo - the same as $tag$foo */ current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE); if (current == NULL) { (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLNode"); return -1; } name.start = (u_char *) JS_AtomToCString(cx, prop); if (name.start == NULL) { return -1; } name.length = njs_strlen(name.start); node = current->node; if (name.length > 1 && name.start[0] == '$') { if (name.length == njs_length("$attrs") && njs_strncmp(&name.start[1], "attrs", njs_length("attrs")) == 0) { JS_FreeCString(cx, (char *) name.start); if (node->properties == NULL) { return 0; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = qjs_xml_attr_make(cx, current->doc, node->properties); if (JS_IsException(pdesc->value)) { return -1; } } return 1; } if (name.length > njs_length("$attr$") && njs_strncmp(&name.start[1], "attr$", njs_length("attr$")) == 0) { nm.length = name.length - njs_length("$attr$"); nm.start = name.start + njs_length("$attr$"); value = qjs_xml_node_attr_handler(cx, current, &nm); JS_FreeCString(cx, (char *) name.start); if (JS_IsException(value)) { return -1; } if (JS_IsUndefined(value)) { return 0; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = value; } return 1; } if (name.length == njs_length("$name") && njs_strncmp(&name.start[1], "name", njs_length("name")) == 0) { JS_FreeCString(cx, (char *) name.start); if (node->type != XML_ELEMENT_NODE) { return 0; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = JS_NewString(cx, (char *) node->name); if (JS_IsException(pdesc->value)) { return -1; } } return 1; } if (name.length == njs_length("$ns") && njs_strncmp(&name.start[1], "ns", njs_length("ns")) == 0) { JS_FreeCString(cx, (char *) name.start); if (node->ns == NULL || node->ns->href == NULL) { return 0; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = JS_NewString(cx, (char *) node->ns->href); if (JS_IsException(pdesc->value)) { return -1; } } return 1; } if (name.length == njs_length("$parent") && njs_strncmp(&name.start[1], "parent", njs_length("parent")) == 0) { JS_FreeCString(cx, (char *) name.start); if (node->parent == NULL || node->parent->type != XML_ELEMENT_NODE) { return 0; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = qjs_xml_node_make(cx, current->doc, node->parent); if (JS_IsException(pdesc->value)) { return -1; } } return 1; } if (name.length == njs_length("$tags") && njs_strncmp(&name.start[1], "tags", njs_length("tags")) == 0) { JS_FreeCString(cx, (char *) name.start); if (pdesc != NULL) { nm.start = NULL; nm.length = 0; pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = qjs_xml_node_tags_handler(cx, current, &nm); if (JS_IsException(pdesc->value)) { return -1; } } return 1; } if (name.length > njs_length("$tags$") && njs_strncmp(&name.start[1], "tags$", njs_length("tags$")) == 0) { nm.start = name.start + njs_length("$tags$"); nm.length = name.length - njs_length("$tags$"); value = qjs_xml_node_tags_handler(cx, current, &nm); JS_FreeCString(cx, (char *) name.start); if (JS_IsException(value)) { return -1; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = value; } return 1; } if (name.length > njs_length("$tag$") && njs_strncmp(&name.start[1], "tag$", njs_length("tag$")) == 0) { nm.length = name.length - njs_length("$tag$"); nm.start = name.start + njs_length("$tag$"); goto tag; } if (name.length == njs_length("$text") && njs_strncmp(&name.start[1], "text", njs_length("text")) == 0) { JS_FreeCString(cx, (char *) name.start); text = xmlNodeGetContent(current->node); if (text == NULL) { return 0; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = JS_NewString(cx, (char *) text); if (JS_IsException(pdesc->value)) { xmlFree(text); return -1; } } xmlFree(text); return 1; } } nm = name; tag: value = qjs_xml_node_tag_handler(cx, current, &nm); JS_FreeCString(cx, (char *) name.start); if (JS_IsException(value)) { return -1; } if (JS_IsUndefined(value)) { return 0; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = value; } return 1; } static int qjs_xml_node_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) { int rc; JSValue keys; xmlNode *node, *current; qjs_xml_node_t *tree; tree = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_NODE); if (tree == NULL) { (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLNode"); return -1; } keys = JS_NewObject(cx); if (JS_IsException(keys)) { return -1; } current = tree->node; if (current->name != NULL && current->type == XML_ELEMENT_NODE) { if (qjs_xml_push_string(cx, keys, "$name") < 0) { goto fail; } } if (current->ns != NULL) { if (qjs_xml_push_string(cx, keys, "$ns") < 0) { goto fail; } } if (current->properties != NULL) { if (qjs_xml_push_string(cx, keys, "$attrs") < 0) { goto fail; } } if (current->children != NULL && current->children->content != NULL) { if (qjs_xml_push_string(cx, keys, "$text") < 0) { goto fail; } } for (node = current->children; node != NULL; node = node->next) { if (node->type != XML_ELEMENT_NODE) { continue; } if (qjs_xml_push_string(cx, keys, "$tags") < 0) { goto fail; } break; } rc = JS_GetOwnPropertyNames(cx, ptab, plen, keys, JS_GPN_STRING_MASK); JS_FreeValue(cx, keys); return rc; fail: JS_FreeValue(cx, keys); return -1; } static int qjs_xml_node_set_property(JSContext *cx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { return qjs_xml_node_define_own_property(cx, obj, atom, value, JS_UNDEFINED, JS_UNDEFINED, flags); } static int qjs_xml_node_delete_property(JSContext *cx, JSValueConst obj, JSAtom prop) { return qjs_xml_node_define_own_property(cx, obj, prop, JS_UNDEFINED, JS_UNDEFINED, JS_UNDEFINED, JS_PROP_THROW); } static int qjs_xml_node_define_own_property(JSContext *cx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst getter, JSValueConst setter, int flags) { int rc; njs_str_t name, nm; name.start = (u_char *) JS_AtomToCString(cx, atom); if (name.start == NULL) { return -1; } name.length = njs_strlen(name.start); if (name.length > 1 && name.start[0] == '$') { if (name.length > njs_length("$attr$") && njs_strncmp(&name.start[1], "attr$", njs_length("attr$")) == 0) { nm.start = name.start + njs_length("$attr$"); rc = qjs_xml_node_attr_modify(cx, obj, nm.start, value); JS_FreeCString(cx, (char *) name.start); return rc; } if (name.length > njs_length("$tag$") && njs_strncmp(&name.start[1], "tag$", njs_length("tag$")) == 0) { nm.start = name.start + njs_length("$tag$"); nm.length = name.length - njs_length("$tag$"); rc = qjs_xml_node_tag_modify(cx, obj, &nm, value); JS_FreeCString(cx, (char *) name.start); return rc; } if (name.length >= njs_length("$tags$") && njs_strncmp(&name.start[1], "tags$", njs_length("tags$")) == 0) { nm.start = name.start + njs_length("$tags$"); nm.length = name.length - njs_length("$tags$"); rc = qjs_xml_node_tags_modify(cx, obj, &nm, value); JS_FreeCString(cx, (char *) name.start); return rc; } if (name.length >= njs_length("$tags") && njs_strncmp(&name.start[1], "tags", njs_length("tags")) == 0) { nm.start = name.start + njs_length("$tags"); nm.length = name.length - njs_length("$tags"); rc = qjs_xml_node_tags_modify(cx, obj, &nm, value); JS_FreeCString(cx, (char *) name.start); return rc; } if (name.length == njs_length("$text") && njs_strncmp(&name.start[1], "text", njs_length("text")) == 0) { JS_FreeCString(cx, (char *) name.start); return qjs_xml_node_text_handler(cx, obj, value); } } rc = qjs_xml_node_tag_modify(cx, obj, &name, value); JS_FreeCString(cx, (char *) name.start); return rc; } static JSValue qjs_xml_node_add_child(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { xmlNode *copy, *node, *rnode; qjs_xml_node_t *current; current = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_XML_NODE); if (current == NULL) { JS_ThrowTypeError(cx, "\"this\" is not a XMLNode object"); 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"); 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; } qjs_xml_replace_node(cx, current, copy); return JS_UNDEFINED; error: xmlFreeNode(copy); return JS_EXCEPTION; } static JSValue qjs_xml_node_remove_children(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { int rc; njs_str_t name; qjs_xml_node_t *current; current = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_XML_NODE); if (current == NULL) { JS_ThrowTypeError(cx, "\"this\" is not a XMLNode object"); return JS_EXCEPTION; } if (!JS_IsNullOrUndefined(argv[0])) { if (!JS_IsString(argv[0])) { JS_ThrowTypeError(cx, "selector is not a string"); return JS_EXCEPTION; } name.start = (u_char *) JS_ToCString(cx, argv[0]); if (name.start == NULL) { return JS_EXCEPTION; } name.length = njs_strlen(name.start); } else { name.start = NULL; name.length = 0; } rc = qjs_xml_node_tag_modify(cx, this_val, &name, JS_UNDEFINED); if (name.start != NULL) { JS_FreeCString(cx, (char *) name.start); } if (rc < 0) { return JS_EXCEPTION; } return JS_UNDEFINED; } static JSValue qjs_xml_node_set_attribute(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { const u_char *name; if (!JS_IsString(argv[0])) { JS_ThrowTypeError(cx, "\"name\" argument is not a string"); return JS_EXCEPTION; } name = (const u_char *) JS_ToCString(cx, argv[0]); if (qjs_xml_node_attr_modify(cx, this_val, name, argv[1]) < 0) { JS_FreeCString(cx, (char *) name); return JS_EXCEPTION; } JS_FreeCString(cx, (char *) name); return JS_UNDEFINED; } static JSValue qjs_xml_node_remove_attribute(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { const u_char *name; if (!JS_IsString(argv[0])) { JS_ThrowTypeError(cx, "\"name\" argument is not a string"); return JS_EXCEPTION; } name = (const u_char *) JS_ToCString(cx, argv[0]); if (qjs_xml_node_attr_modify(cx, this_val, name, JS_UNDEFINED) < 0) { JS_FreeCString(cx, (char *) name); return JS_EXCEPTION; } JS_FreeCString(cx, (char *) name); return JS_UNDEFINED; } static JSValue qjs_xml_node_remove_all_attributes(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { xmlNode *node; qjs_xml_node_t *current; current = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_XML_NODE); if (current == NULL) { JS_ThrowTypeError(cx, "\"this\" is not a XMLNode object"); return JS_EXCEPTION; } node = current->node; if (node->properties != NULL) { xmlFreePropList(node->properties); node->properties = NULL; } return JS_UNDEFINED; } static JSValue qjs_xml_node_set_text(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { if (qjs_xml_node_text_handler(cx, this_val, argv[0]) < 0) { return JS_EXCEPTION; } return JS_UNDEFINED; } static JSValue qjs_xml_node_remove_text(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { if (qjs_xml_node_text_handler(cx, this_val, JS_UNDEFINED) < 0) { return JS_EXCEPTION; } return JS_UNDEFINED; } static void qjs_xml_node_finalizer(JSRuntime *rt, JSValue val) { qjs_xml_node_t *node; node = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_NODE); qjs_xml_doc_free(rt, node->doc); js_free_rt(rt, node); } static xmlNode * qjs_xml_node(JSContext *cx, JSValueConst val, xmlDoc **doc) { qjs_xml_doc_t *tree; qjs_xml_node_t *current; current = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_NODE); if (current == NULL) { tree = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_DOC); if (tree == NULL) { JS_ThrowInternalError(cx, "'this' is not XMLNode or XMLDoc"); return NULL; } if (doc != NULL) { *doc = tree->doc; } return xmlDocGetRootElement(tree->doc); } if (doc != NULL) { *doc = current->doc->doc; } return current->node; } static JSValue qjs_xml_attr_make(JSContext *cx, qjs_xml_doc_t *doc, xmlAttr *attr) { JSValue ret; qjs_xml_attr_t *current; current = js_malloc(cx, sizeof(qjs_xml_attr_t)); if (current == NULL) { JS_ThrowOutOfMemory(cx); return JS_EXCEPTION; } current->attr = attr; current->doc = doc; doc->ref_count++; ret = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_XML_ATTR); if (JS_IsException(ret)) { js_free(cx, current); return ret; } JS_SetOpaque(ret, current); return ret; } static int qjs_xml_attr_get_own_property(JSContext *cx, JSPropertyDescriptor *pdesc, JSValueConst obj, JSAtom prop) { size_t size; u_char *text; xmlAttr *attr; njs_str_t name; qjs_xml_attr_t *current; current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_ATTR); if (current == NULL) { (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLAttr"); return -1; } name.start = (u_char *) JS_AtomToCString(cx, prop); if (name.start == NULL) { return -1; } name.length = njs_strlen(name.start); for (attr = current->attr; 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; } JS_FreeCString(cx, (char *) name.start); text = xmlNodeGetContent(attr->children); if (text == NULL) { return 0; } if (pdesc != NULL) { pdesc->flags = JS_PROP_ENUMERABLE; pdesc->getter = JS_UNDEFINED; pdesc->setter = JS_UNDEFINED; pdesc->value = JS_NewString(cx, (char *) text); if (JS_IsException(pdesc->value)) { xmlFree(text); return -1; } } xmlFree(text); return 1; } JS_FreeCString(cx, (char *) name.start); return 0; } static int qjs_xml_attr_get_own_property_names(JSContext *cx, JSPropertyEnum **ptab, uint32_t *plen, JSValueConst obj) { int rc; JSValue keys; xmlAttr *attr; qjs_xml_attr_t *current; current = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_XML_ATTR); if (current == NULL) { (void) JS_ThrowInternalError(cx, "\"this\" is not an XMLAttr"); return -1; } keys = JS_NewObject(cx); if (JS_IsException(keys)) { return -1; } for (attr = current->attr; attr != NULL; attr = attr->next) { if (attr->type != XML_ATTRIBUTE_NODE) { continue; } if (qjs_xml_push_string(cx, keys, (char *) attr->name) < 0) { goto fail; } } rc = JS_GetOwnPropertyNames(cx, ptab, plen, keys, JS_GPN_STRING_MASK); JS_FreeValue(cx, keys); return rc; fail: JS_FreeValue(cx, keys); return -1; } static void qjs_xml_attr_finalizer(JSRuntime *rt, JSValue val) { qjs_xml_attr_t *attr; attr = JS_GetOpaque(val, QJS_CORE_CLASS_ID_XML_ATTR); qjs_xml_doc_free(rt, attr->doc); js_free_rt(rt, attr); } static u_char ** qjs_xml_parse_ns_list(JSContext *cx, u_char *src) { u_char *p, **buf, **out; size_t size, idx; size = 8; p = src; buf = js_mallocz(cx, size * sizeof(char *)); if (buf == NULL) { JS_ThrowOutOfMemory(cx); return NULL; } out = buf; while (*p != '\0') { idx = out - buf; if (idx >= size) { size *= 2; buf = js_realloc(cx, buf, size * sizeof(char *)); if (buf == NULL) { JS_ThrowOutOfMemory(cx); return NULL; } out = &buf[idx]; } *out++ = p; while (*p != ' ' && *p != '\0') { p++; } if (*p == ' ') { *p++ = '\0'; } } *out = NULL; return buf; } static int qjs_xml_node_one_contains(qjs_xml_nset_t *nset, xmlNode *node, xmlNode *parent) { int in; xmlNs ns; if (nset->type == XML_NSET_TREE_NO_COMMENTS && node->type == XML_COMMENT_NODE) { return 0; } in = 1; if (nset->nodes != NULL) { if (node->type != XML_NAMESPACE_DECL) { in = xmlXPathNodeSetContains(nset->nodes, node); } else { memcpy(&ns, node, sizeof(ns)); /* libxml2 workaround, check xpath.c for details */ if ((parent != NULL) && (parent->type == XML_ATTRIBUTE_NODE)) { ns.next = (xmlNs *) parent->parent; } else { ns.next = (xmlNs *) parent; } in = xmlXPathNodeSetContains(nset->nodes, (xmlNode *) &ns); } } switch (nset->type) { case XML_NSET_TREE: case XML_NSET_TREE_NO_COMMENTS: if (in != 0) { return 1; } if ((parent != NULL) && (parent->type == XML_ELEMENT_NODE)) { return qjs_xml_node_one_contains(nset, parent, parent->parent); } return 0; case XML_NSET_TREE_INVERT: default: if (in != 0) { return 0; } if ((parent != NULL) && (parent->type == XML_ELEMENT_NODE)) { return qjs_xml_node_one_contains(nset, parent, parent->parent); } } return 1; } static int qjs_xml_c14n_visibility_cb(void *user_data, xmlNode *node, xmlNode *parent) { int status; qjs_xml_nset_t *n, *nset; nset = user_data; if (nset == NULL) { return 1; } status = 1; n = nset; do { if (status && !qjs_xml_node_one_contains(n, node, parent)) { status = 0; } n = n->next; } while (n != nset); return status; } static int qjs_xml_buf_write_cb(void *context, const char *buffer, int len) { njs_chb_t *chain = context; njs_chb_append(chain, buffer, len); return chain->error ? -1 : len; } static qjs_xml_nset_t * qjs_xml_nset_create(JSContext *cx, xmlDoc *doc, xmlNode *current, qjs_xml_nset_type_t type) { xmlNodeSet *nodes; qjs_xml_nset_t *nset; nset = js_mallocz(cx, sizeof(qjs_xml_nset_t)); if (nset == NULL) { JS_ThrowOutOfMemory(cx); return NULL; } nodes = xmlXPathNodeSetCreate(current); if (nodes == NULL) { js_free(cx, nset); JS_ThrowOutOfMemory(cx); return NULL; } nset->doc = doc; nset->type = type; nset->nodes = nodes; nset->next = nset->prev = nset; return nset; } static qjs_xml_nset_t * qjs_xml_nset_add(qjs_xml_nset_t *nset, qjs_xml_nset_t *add) { if (nset == NULL) { return add; } add->next = nset; add->prev = nset->prev; nset->prev->next = add; nset->prev = add; return nset; } static void qjs_xml_nset_free(JSContext *cx, qjs_xml_nset_t *nset) { if (nset == NULL) { return; } if (nset->nodes != NULL) { xmlXPathFreeNodeSet(nset->nodes); } js_free(cx, nset); } static int qjs_xml_encode_special_chars(JSContext *cx, njs_str_t *src, njs_str_t *out) { size_t len; u_char *p, *dst, *end; len = 0; end = src->start + src->length; for (p = src->start; p < end; p++) { if (*p == '<' || *p == '>') { len += njs_length("<"); } if (*p == '&' || *p == '\r') { len += njs_length("&"); } if (*p == '"') { len += njs_length("""); } len += 1; } if (len == 0) { out->start = NULL; out->length = 0; return 0; } out->start = js_malloc(cx, len); if (out->start == NULL) { JS_ThrowOutOfMemory(cx); return -1; } dst = out->start; for (p = src->start; p < end; p++) { if (*p == '<') { *dst++ = '&'; *dst++ = 'l'; *dst++ = 't'; *dst++ = ';'; } else if (*p == '>') { *dst++ = '&'; *dst++ = 'g'; *dst++ = 't'; *dst++ = ';'; } else if (*p == '&') { *dst++ = '&'; *dst++ = 'a'; *dst++ = 'm'; *dst++ = 'p'; *dst++ = ';'; } else if (*p == '"') { *dst++ = '&'; *dst++ = 'q'; *dst++ = 'u'; *dst++ = 'o'; *dst++ = 't'; *dst++ = ';'; } else if (*p == '\r') { *dst++ = '&'; *dst++ = '#'; *dst++ = '1'; *dst++ = '3'; *dst++ = ';'; } else { *dst++ = *p; } } out->length = len; return 0; } static void qjs_xml_replace_node(JSContext *cx, qjs_xml_node_t *node, xmlNode *current) { xmlNode *old; old = node->node; if (current != NULL) { old = xmlReplaceNode(old, current); } else { xmlUnlinkNode(old); } old->next = node->doc->free; node->doc->free = old; } static void qjs_xml_error(JSContext *cx, qjs_xml_doc_t *current, const char *fmt, ...) { u_char *p, *last; va_list args; const xmlError *err; u_char errstr[NJS_MAX_ERROR_STR]; last = &errstr[NJS_MAX_ERROR_STR]; va_start(args, fmt); p = njs_vsprintf(errstr, last - 1, fmt, args); va_end(args); err = xmlCtxtGetLastError(current->ctx); if (err != NULL) { p = njs_sprintf(p, last - 1, " (libxml2: \"%*s\" at %d:%d)", njs_strlen(err->message) - 1, err->message, err->line, err->int2); } JS_ThrowSyntaxError(cx, "%.*s", (int) (p - errstr), errstr); } static int qjs_xml_module_init(JSContext *cx, JSModuleDef *m) { JSValue proto; proto = JS_NewObject(cx); if (JS_IsException(proto)) { return -1; } JS_SetPropertyFunctionList(cx, proto, qjs_xml_export, njs_nitems(qjs_xml_export)); if (JS_SetModuleExport(cx, m, "default", proto) != 0) { return -1; } return JS_SetModuleExportList(cx, m, qjs_xml_export, njs_nitems(qjs_xml_export)); } static JSModuleDef * qjs_xml_init(JSContext *cx, const char *name) { int rc; JSValue proto; JSModuleDef *m; if (!JS_IsRegisteredClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_XML_DOC)) { if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_XML_DOC, &qjs_xml_doc_class) < 0) { return NULL; } proto = JS_NewObject(cx); if (JS_IsException(proto)) { return NULL; } JS_SetPropertyFunctionList(cx, proto, qjs_xml_doc_proto, njs_nitems(qjs_xml_doc_proto)); JS_SetClassProto(cx, QJS_CORE_CLASS_ID_XML_DOC, proto); if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_XML_NODE, &qjs_xml_node_class) < 0) { return NULL; } proto = JS_NewObject(cx); if (JS_IsException(proto)) { return NULL; } JS_SetPropertyFunctionList(cx, proto, qjs_xml_node_proto, njs_nitems(qjs_xml_node_proto)); JS_SetClassProto(cx, QJS_CORE_CLASS_ID_XML_NODE, proto); if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_XML_ATTR, &qjs_xml_attr_class) < 0) { return NULL; } proto = JS_NewObject(cx); if (JS_IsException(proto)) { return NULL; } JS_SetPropertyFunctionList(cx, proto, qjs_xml_attr_proto, njs_nitems(qjs_xml_attr_proto)); JS_SetClassProto(cx, QJS_CORE_CLASS_ID_XML_ATTR, proto); } m = JS_NewCModule(cx, name, qjs_xml_module_init); if (m == NULL) { return NULL; } JS_AddModuleExport(cx, m, "default"); rc = JS_AddModuleExportList(cx, m, qjs_xml_export, njs_nitems(qjs_xml_export)); if (rc != 0) { return NULL; } return m; }