diff --git a/test/test-html-build.lua b/test/test-html-build.lua index ad888b2..8c7818a 100644 --- a/test/test-html-build.lua +++ b/test/test-html-build.lua @@ -26,7 +26,7 @@ function TestHTMLBuild.test_empty_root_dtd_systemid() local uri = "file:///usr/local/share/test.dtd" local document = HTML.build({"html"}, uri) luaunit.assertEquals({ - ffi.string(document.document.intSubset.SystemID), + ffi.string(document.raw_document.intSubset.SystemID), document:to_html() }, { @@ -43,8 +43,8 @@ function TestHTMLBuild.test_empty_root_dtd_publicid() local public_id = "-//W3C//DTD HTML 4.01//EN" local document = HTML.build({"html"}, uri, public_id) luaunit.assertEquals({ - ffi.string(document.document.intSubset.SystemID), - ffi.string(document.document.intSubset.ExternalID), + ffi.string(document.raw_document.intSubset.SystemID), + ffi.string(document.raw_document.intSubset.ExternalID), document:to_html() }, { diff --git a/xmlua/document.lua b/xmlua/document.lua index f3e43de..016696d 100644 --- a/xmlua/document.lua +++ b/xmlua/document.lua @@ -3,7 +3,6 @@ local Document = {} local libxml2 = require("xmlua.libxml2") local ffi = require("ffi") local converter = require("xmlua.converter") -local to_string = converter.to_string local Serializable = require("xmlua.serializable") local Searchable = require("xmlua.searchable") @@ -40,11 +39,11 @@ function metatable.__index(document, key) end function methods:root() - local root_element = libxml2.xmlDocGetRootElement(self.document) - if not root_element then + local node = libxml2.xmlDocGetRootElement(self.raw_document) + if not node then return nil end - return Element.new(self.document, root_element) + return Element.new(self, node) end function methods:parent() @@ -52,42 +51,42 @@ function methods:parent() end function methods:encoding() - return ffi.string(self.document.encoding) + return ffi.string(self.raw_document.encoding) end function methods:create_cdata_section(data) local raw_cdata_section_node = - libxml2.xmlNewCDataBlock(self.document, + libxml2.xmlNewCDataBlock(self.raw_document, data, data:len()) - return CDATASection.new(self.document, raw_cdata_section_node) + return CDATASection.new(self, raw_cdata_section_node) end function methods:create_comment(data) local raw_comment_node = libxml2.xmlNewComment(data) - return Comment.new(self.document, raw_comment_node) + return Comment.new(self, raw_comment_node) end function methods:create_document_fragment() local raw_document_fragment_node = - libxml2.xmlNewDocFragment(self.document) - return DocumentFragment.new(self.document, + libxml2.xmlNewDocFragment(self.raw_document) + return DocumentFragment.new(self, raw_document_fragment_node) end function methods:create_document_type(name, external_id, system_id) local raw_document_type = - libxml2.xmlCreateIntSubset(self.document, name, external_id, system_id) - return DocumentType.new(self.document, + libxml2.xmlCreateIntSubset(self.raw_document, name, external_id, system_id) + return DocumentType.new(self, raw_document_type) end function methods:get_internal_subset() local raw_document_type = - libxml2.xmlGetIntSubset(self.document) + libxml2.xmlGetIntSubset(self.raw_document) if raw_document_type ~= nil then - return DocumentType.new(self.document, + return DocumentType.new(self, raw_document_type) else return nil @@ -96,28 +95,28 @@ end function methods:add_entity_reference(name) local raw_entity_reference = - libxml2.xmlNewReference(self.document, name) - return EntityReference.new(self.document, + libxml2.xmlNewReference(self.raw_document, name) + return EntityReference.new(self, raw_entity_reference) end function methods:create_namespace(href, prefix) local raw_namespace = libxml2.xmlNewNs(self.node, href, prefix) - return Namespace.new(self.document, raw_namespace) + return Namespace.new(self, raw_namespace) end function methods:create_processing_instruction(name, content) local raw_processing_instruction = libxml2.xmlNewPI(name, content) - return ProcessingInstruction.new(self.document, + return ProcessingInstruction.new(self, raw_processing_instruction) end function methods:add_entity(entity_info) local entity_type_name = entity_info["entity_type"] local entity_type = converter.convert_entity_type_name(entity_type_name) - local raw_entity = libxml2.xmlAddDocEntity(self.document, + local raw_entity = libxml2.xmlAddDocEntity(self.raw_document, entity_info["name"], entity_type, entity_info["external_id"], @@ -127,14 +126,14 @@ function methods:add_entity(entity_info) end function methods:get_entity(name) - local raw_entity = libxml2.xmlGetDocEntity(self.document, name) + local raw_entity = libxml2.xmlGetDocEntity(self.raw_document, name) return converter.convert_xml_entity(raw_entity) end function methods:add_dtd_entity(entity_info) local entity_type_name = entity_info["entity_type"] local entity_type = converter.convert_entity_type_name(entity_type_name) - local raw_dtd_entity = libxml2.xmlAddDtdEntity(self.document, + local raw_dtd_entity = libxml2.xmlAddDtdEntity(self.raw_document, entity_info["name"], entity_type, entity_info["external_id"], @@ -144,7 +143,7 @@ function methods:add_dtd_entity(entity_info) end function methods:get_dtd_entity(name) - local raw_dtd_entity = libxml2.xmlGetDtdEntity(self.document, name) + local raw_dtd_entity = libxml2.xmlGetDtdEntity(self.raw_document, name) return converter.convert_xml_entity(raw_dtd_entity) end @@ -167,7 +166,7 @@ function Document.build(raw_document, tree) local root = Element.build(document, tree[1], tree[2]) if not libxml2.xmlDocSetRootElement(raw_document, root.node) then - root:unlink() + --root:unlink() return nil end @@ -187,7 +186,7 @@ function Document.new(raw_document, errors) errors = {} end local document = { - document = raw_document, + raw_document = raw_document, errors = errors, } setmetatable(document, metatable) diff --git a/xmlua/element.lua b/xmlua/element.lua index 8afebfa..0578252 100644 --- a/xmlua/element.lua +++ b/xmlua/element.lua @@ -88,7 +88,7 @@ local function remove_namespace(node, prefix) end end - namespace = node.nsDef + local namespace = node.nsDef local namespace_previous = nil while namespace ~= ffi.NULL do if is_target_namespace(namespace) then @@ -127,65 +127,67 @@ local function set_attributes(element, attributes) end end -local function create_sub_element(document, node, name, attributes) +local function create_sub_element(document, parent, name, attributes) local namespace_prefix, local_name = parse_name(name) - local raw_element = libxml2.xmlNewNode(nil, local_name) - local element = Element.new(document, raw_element) + local node = libxml2.xmlNewNode(nil, local_name) + local element = Element.new(document, node) set_attributes(element, attributes) - local namespace = libxml2.xmlSearchNs(document, raw_element, namespace_prefix) - if not namespace and node then - namespace = libxml2.xmlSearchNs(document, node, namespace_prefix) + local namespace = libxml2.xmlSearchNs(document.raw_document, node, namespace_prefix) + if not namespace and parent then + namespace = libxml2.xmlSearchNs(document.raw_document, parent, namespace_prefix) end if namespace then - libxml2.xmlSetNs(raw_element, namespace) + libxml2.xmlSetNs(node, namespace) elseif namespace_prefix then element:unlink() - raw_element = libxml2.xmlNewNode(nil, name) - element = Element.new(document, raw_element) + node = libxml2.xmlNewNode(nil, name) + element = Element.new(document, node) set_attributes(element, attributes) end return element end -function methods:add_child(node) - if node.node.parent ~= ffi.NULL then - node:unlink() - end - local raw_added_node = - libxml2.xmlAddChild(self.node, node.node) - if raw_added_node ~= ffi.NULL then - ffi.gc(node.node, nil) +function methods:add_child(element) + local node + if self.document == element.document then + if element.node.parent ~= ffi.NULL then + element:unlink() + end + node = element.node + else + node = libxml2.xmlCopyNode(element.node, self.document.raw_document) end + libxml2.xmlAddChild(self.node, node) end -function methods:add_previous_sibling(node) - if not self.node and not node.node then +function methods:add_previous_sibling(element) + if not self.node and not element.node then error("Already freed receiver node and added node") elseif not self.node then error("Already freed receiver node") - elseif not node.node then + elseif not element.node then error("Already freed added node") end local raw_added_node, was_freed = - libxml2.xmlAddPrevSibling(self.node, node.node) + libxml2.xmlAddPrevSibling(self.node, element.node) if was_freed then - node.node = nil + element.node = nil end end -function methods:append_sibling(node) - if not self.node and not node.node then +function methods:append_sibling(element) + if not self.node and not element.node then error("Already freed receiver node and appended node") elseif not self.node then error("Already freed receiver node") - elseif not node.node then + elseif not element.node then error("Already freed appended node") end - local was_freed = libxml2.xmlAddSibling(self.node, node.node) + local was_freed = libxml2.xmlAddSibling(self.node, element.node) if was_freed then - node.node = nil + element.node = nil end end @@ -256,28 +258,28 @@ function methods:insert_element(position, name, attributes) end function methods:unlink() - local unlinked_node = Node.unlink(self) - return Element.new(nil, unlinked_node) + Node.unlink(self) + return self end function methods:get_attribute(name) local namespace_prefix, local_name = parse_name(name) if namespace_prefix == "xmlns" then - local namespace = libxml2.xmlSearchNs(self.document, self.node, local_name) + local namespace = libxml2.xmlSearchNs(self.document.raw_document, self.node, local_name) if namespace then return ffi.string(namespace.href) else return nil end elseif namespace_prefix == ffi.NULL and local_name == "xmlns" then - local namespace = libxml2.xmlSearchNs(self.document, self.node, nil) + local namespace = libxml2.xmlSearchNs(self.document.raw_document, self.node, nil) if namespace then return ffi.string(namespace.href) else return nil end elseif namespace_prefix then - local namespace = libxml2.xmlSearchNs(self.document, + local namespace = libxml2.xmlSearchNs(self.document.raw_document, self.node, namespace_prefix) if namespace then @@ -298,7 +300,7 @@ function methods:set_attribute(name, value) local namespace_prefix, local_name = parse_name(name) local namespace if namespace_prefix == "xmlns" then - namespace = libxml2.xmlSearchNs(self.document, + namespace = libxml2.xmlSearchNs(self.document.raw_document, self.node, local_name) if namespace then @@ -308,7 +310,7 @@ function methods:set_attribute(name, value) libxml2.xmlNewNs(self.node, value, local_name) end elseif namespace_prefix == nil and local_name == "xmlns" then - namespace = libxml2.xmlSearchNs(self.document, self.node, nil) + namespace = libxml2.xmlSearchNs(self.document.raw_document, self.node, nil) if namespace then libxml2.xmlFree(ffi.cast("void *", namespace.href)) namespace.href = libxml2.xmlStrdup(value) @@ -317,7 +319,7 @@ function methods:set_attribute(name, value) set_default_namespace(self.node, namespace) end elseif namespace_prefix then - namespace = libxml2.xmlSearchNs(self.document, + namespace = libxml2.xmlSearchNs(self.document.raw_document, self.node, namespace_prefix) if namespace then @@ -341,7 +343,7 @@ function methods:remove_attribute(name) elseif namespace_prefix == nil and local_name == "xmlns" then remove_namespace(self.node, nil) elseif namespace_prefix then - namespace = libxml2.xmlSearchNs(self.document, + namespace = libxml2.xmlSearchNs(self.document.raw_document, self.node, namespace_prefix) if namespace then @@ -378,12 +380,12 @@ function methods:next() end function methods:root() - return Document.new(self.document):root() + return self.document:root() end function methods:parent() if tonumber(self.node.parent.type) == ffi.C.XML_DOCUMENT_NODE then - return Document.new(self.document) + return self.document else return Element.new(self.document, self.node.parent) end @@ -404,7 +406,7 @@ function methods:text() end function methods:namespaces() - local raw_namespaces = libxml2.xmlGetNsList(self.document, self.node) + local raw_namespaces = libxml2.xmlGetNsList(self.document.raw_document, self.node) if not raw_namespaces then return nil end @@ -427,23 +429,23 @@ function methods:find_namespace(prefix, href) local raw_namespace if not prefix and href then raw_namespace = - libxml2.xmlSearchNsByHref(self.document, self.node, href) + libxml2.xmlSearchNsByHref(self.document.raw_document, self.node, href) else raw_namespace = - libxml2.xmlSearchNs(self.document, self.node, prefix) + libxml2.xmlSearchNs(self.document.raw_document, self.node, prefix) end return Namespace.new(self.document, raw_namespace) end -- For internal use function Element.build(document, name, attributes) - return create_sub_element(document.node, nil, name, attributes) + return create_sub_element(document, nil, name, attributes) end function Element.new(document, node) local element = { document = document, - node = node, + node = node } setmetatable(element, metatable) return element diff --git a/xmlua/libxml2.lua b/xmlua/libxml2.lua index 366b5bd..760638a 100644 --- a/xmlua/libxml2.lua +++ b/xmlua/libxml2.lua @@ -16,6 +16,10 @@ require("xmlua.libxml2.xmlsave") require("xmlua.libxml2.xpath") require("xmlua.libxml2.entities") +local function print_dbg(...) + --print(...) +end + local ffi = require("ffi") local loaded, xml2 = pcall(ffi.load, "xml2") if not loaded then @@ -116,7 +120,11 @@ function libxml2.htmlCtxtReadMemory(context, html, options) if document == ffi.NULL then return nil end - return ffi.gc(document, libxml2.xmlFreeDoc) + print_dbg("htmlCtxtReadMemory: ", document) + return ffi.gc(document, function(p) + print_dbg("xmlFreeDoc: ", p) + xml2.xmlFreeNode(p) + end) end jit.off(libxml2.htmlCtxtReadMemory) @@ -133,7 +141,11 @@ function libxml2.xmlNewParserCtxt() if context == ffi.NULL then return nil end - return ffi.gc(context, xml2.xmlFreeParserCtxt) + print_dbg("xmlNewParserCtxt: ", context) + return ffi.gc(context, function(p) + print_dbg("xmlFreeParserCtxt: ", p) + xml2.xmlFreeParserCtxt(p) + end) end function libxml2.xmlCreatePushParserCtxt(filename) @@ -197,7 +209,12 @@ function libxml2.xmlCtxtReadMemory(context, xml, options) if document == ffi.NULL then return nil end - return ffi.gc(document, libxml2.xmlFreeDoc) + print_dbg("xmlCtxtReadMemory: ", document, ", ctx=", context) + return ffi.gc(document, function(p) + print_dbg("xmlFreeDoc: ", p, ", ctx=", context) + xml2.xmlFreeDoc(p) + end) + end jit.off(libxml2.xmlCtxtReadMemory) @@ -289,7 +306,11 @@ function libxml2.xmlNewDoc(xml_version) if document == ffi.NULL then return nil end - return ffi.gc(document, libxml2.xmlFreeDoc) + print_dbg("xmlNewDoc: ", document) + return ffi.gc(document, function(p) + print_dbg("xmlFreeDoc: ", p) + xml2.xmlFreeDoc(p) + end) end function libxml2.xmlDocSetRootElement(document, root) @@ -572,10 +593,21 @@ end function libxml2.xmlUnlinkNode(node) xml2.xmlUnlinkNode(node) - xml2.xmlSetTreeDoc(node, ffi.NULL) - return ffi.gc(node, xml2.xmlFreeNode) + print_dbg("xmlUnlinkNode: ", node, ", doc=", node.doc) + return ffi.gc(node, function(p) + print_dbg("xmlFreeNode: ", p, ", doc=", p.doc) + xml2.xmlFreeNode(p) + end) +end + +function libxml2.xmlCopyNode(node, doc) + return xml2.xmlDocCopyNode(node, doc, 2) end +function libxml2.xmlFreeNode(node) + print_dbg("xmlFreeNode: ", node, ", doc=", node.doc) + xml2.xmlFreeNode(node) +end function libxml2.xmlBufferCreate() return ffi.gc(xml2.xmlBufferCreate(), xml2.xmlBufferFree) @@ -643,10 +675,15 @@ function libxml2.xmlXPathEvalExpression(expression, context) if object == ffi.NULL then return nil end - return ffi.gc(object, xml2.xmlXPathFreeObject) + return object end jit.off(libxml2.xmlXPathEvalExpression) +function libxml2.xmlXPathFreeObject(object) + xml2.xmlXPathFreeObject(object) +end +jit.off(libxml2.xmlXPathFreeObject) + function libxml2.xmlXPathRegisterNs(context, prefix, namespace_uri) local status = xml2.xmlXPathRegisterNs(context, prefix, namespace_uri) return status == 0 diff --git a/xmlua/libxml2/tree.lua b/xmlua/libxml2/tree.lua index 1c82a77..b2a1491 100644 --- a/xmlua/libxml2/tree.lua +++ b/xmlua/libxml2/tree.lua @@ -342,4 +342,5 @@ xmlChar *xmlGetNodePath(const xmlNode *node); void xmlFreeNode(xmlNodePtr cur); void xmlUnlinkNode(xmlNodePtr cur); void xmlSetTreeDoc(xmlNodePtr tree, xmlDocPtr doc); +xmlNodePtr xmlDocCopyNode(xmlNodePtr node, xmlDocPtr doc, int extended); ]] diff --git a/xmlua/node.lua b/xmlua/node.lua index 2df258c..68bf24d 100644 --- a/xmlua/node.lua +++ b/xmlua/node.lua @@ -36,6 +36,8 @@ function Node:path() end function Node:unlink() - return libxml2.xmlUnlinkNode(self.node) + libxml2.xmlUnlinkNode(self.node) + return self end + return Node diff --git a/xmlua/searchable.lua b/xmlua/searchable.lua index 0e24b87..922f253 100644 --- a/xmlua/searchable.lua +++ b/xmlua/searchable.lua @@ -51,8 +51,9 @@ local ERROR_MESSAGES = { } function Searchable:search(xpath, namespaces) - local document = self.document - local context = libxml2.xmlXPathNewContext(document) + local document = self.document or self + local raw_document = document.raw_document + local context = libxml2.xmlXPathNewContext(raw_document) if not context then error("failed to create XPath context") end @@ -96,6 +97,7 @@ function Searchable:search(xpath, namespaces) if type == ffi.C.XPATH_NODESET then local found_node_set = object.nodesetval if found_node_set == ffi.NULL then + libxml2.xmlXPathFreeObject(object) return NodeSet.new({}) end local raw_node_set = {} @@ -120,7 +122,10 @@ function Searchable:search(xpath, namespaces) -- table.insert(raw_node_set, node) end end + libxml2.xmlXPathFreeObject(object) return NodeSet.new(raw_node_set) + else + libxml2.xmlXPathFreeObject(object) end end diff --git a/xmlua/serializable.lua b/xmlua/serializable.lua index bb7fd08..d498924 100644 --- a/xmlua/serializable.lua +++ b/xmlua/serializable.lua @@ -17,7 +17,7 @@ local function save(target, flags, failure_message, options) if target.node then success = libxml2.xmlSaveTree(context, target.node) else - success = libxml2.xmlSaveDoc(context, target.document) + success = libxml2.xmlSaveDoc(context, target.raw_document) end libxml2.xmlSaveClose(context) if not success then diff --git a/xmlua/xml.lua b/xmlua/xml.lua index 3b238f0..1f48a74 100644 --- a/xmlua/xml.lua +++ b/xmlua/xml.lua @@ -23,7 +23,10 @@ function XML.parse(xml, options) error("Failed to parse XML: " .. ffi.string(context.lastError.message)) end end - return Document.new(document) + local doc = Document.new(document) + -- attach context to extend lifetime of dictionaries + doc.__context__ = context + return doc end return XML