/* :nodoc: */ static VALUE reparent_node_with(VALUE pivot_obj, VALUE reparentee_obj, pivot_reparentee_func prf) { VALUE reparented_obj ; xmlNodePtr reparentee, pivot, reparented, next_text, new_next_text ; if(!rb_obj_is_kind_of(reparentee_obj, cNokogiriXmlNode)) rb_raise(rb_eArgError, "node must be a Nokogiri::XML::Node"); if(rb_obj_is_kind_of(reparentee_obj, cNokogiriXmlDocument)) rb_raise(rb_eArgError, "node must be a Nokogiri::XML::Node"); Data_Get_Struct(reparentee_obj, xmlNode, reparentee); Data_Get_Struct(pivot_obj, xmlNode, pivot); if(XML_DOCUMENT_NODE == reparentee->type || XML_HTML_DOCUMENT_NODE == reparentee->type) rb_raise(rb_eArgError, "cannot reparent a document node"); xmlUnlinkNode(reparentee); if (reparentee->doc != pivot->doc || reparentee->type == XML_TEXT_NODE) { /* * if the reparentee is a text node, there's a very good chance it will be * merged with an adjacent text node after being reparented, and in that case * libxml will free the underlying C struct. * * since we clearly have a ruby object which references the underlying * memory, we can't let the C struct get freed. let's pickle the original * reparentee by rooting it; and then we'll reparent a duplicate of the * node that we don't care about preserving. * * alternatively, if the reparentee is from a different document than the * pivot node, libxml2 is going to get confused about which document's * "dictionary" the node's strings belong to (this is an otherwise * uninteresting libxml2 implementation detail). as a result, we cannot * reparent the actual reparentee, so we reparent a duplicate. */ NOKOGIRI_ROOT_NODE(reparentee); if (!(reparentee = xmlDocCopyNode(reparentee, pivot->doc, 1))) { rb_raise(rb_eRuntimeError, "Could not reparent node (xmlDocCopyNode)"); } } if (reparentee->type == XML_TEXT_NODE && pivot->next && pivot->next->type == XML_TEXT_NODE) { /* * libxml merges text nodes in a right-to-left fashion, meaning that if * there are two text nodes who would be adjacent, the right (or following, * or next) node will be merged into the left (or preceding, or previous) * node. * * and by "merged" I mean the string contents will be concatenated onto the * left node's contents, and then the node will be freed. * * which means that if we have a ruby object wrapped around the right node, * its memory would be freed out from under it. * * so, we detect this edge case and unlink-and-root the text node before it gets * merged. then we dup the node and insert that duplicate back into the * document where the real node was. * * yes, this is totally lame. */ next_text = pivot->next ; new_next_text = xmlDocCopyNode(next_text, pivot->doc, 1) ; xmlUnlinkNode(next_text); NOKOGIRI_ROOT_NODE(next_text); xmlAddNextSibling(pivot, new_next_text); } /* TODO: I really want to remove this. We shouldn't support 2.6.16 anymore */ if ( reparentee->type == XML_TEXT_NODE && pivot->type == XML_TEXT_NODE && is_2_6_16() ) { /* work around a string-handling bug in libxml 2.6.16. we'd rather leak than segfault. */ pivot->content = xmlStrdup(pivot->content); } if(!(reparented = (*prf)(pivot, reparentee))) { rb_raise(rb_eRuntimeError, "Could not reparent node"); } /* * make sure the ruby object is pointed at the just-reparented node, which * might be a duplicate (see above) or might be the result of merging * adjacent text nodes. */ DATA_PTR(reparentee_obj) = reparented ; relink_namespace(reparented); reparented_obj = Nokogiri_wrap_xml_node(Qnil, reparented); rb_funcall(reparented_obj, decorate_bang, 0); return reparented_obj ; }
/* :nodoc: */ static VALUE reparent_node_with(VALUE node_obj, VALUE other_obj, node_other_func func) { VALUE reparented_obj ; xmlNodePtr node, other, reparented ; if(!rb_obj_is_kind_of(node_obj, cNokogiriXmlNode)) rb_raise(rb_eArgError, "node must be a Nokogiri::XML::Node"); Data_Get_Struct(node_obj, xmlNode, node); Data_Get_Struct(other_obj, xmlNode, other); if(XML_DOCUMENT_NODE == node->type || XML_HTML_DOCUMENT_NODE == node->type) rb_raise(rb_eArgError, "cannot reparent a document node"); if(node->type == XML_TEXT_NODE) { NOKOGIRI_ROOT_NODE(node); node = xmlDocCopyNode(node, other->doc, 1); } if (node->doc == other->doc) { xmlUnlinkNode(node) ; // TODO: I really want to remove this. We shouldn't support 2.6.16 anymore if ( node->type == XML_TEXT_NODE && other->type == XML_TEXT_NODE && is_2_6_16() ) { // we'd rather leak than segfault. other->content = xmlStrdup(other->content); } if(!(reparented = (*func)(other, node))) { rb_raise(rb_eRuntimeError, "Could not reparent node (%s:%d)", __FILE__, __LINE__); } } else { xmlNodePtr duped_node ; // recursively copy to the new document if (!(duped_node = xmlDocCopyNode(node, other->doc, 1))) { rb_raise(rb_eRuntimeError, "Could not reparent node (xmlDocCopyNode)"); } if(!(reparented = (*func)(other, duped_node))) { rb_raise(rb_eRuntimeError, "Could not reparent node (%s:%d)", __FILE__, __LINE__); } xmlUnlinkNode(node); NOKOGIRI_ROOT_NODE(node); } // the child was a text node that was coalesced. we need to have the object // point at SOMETHING, or we'll totally bomb out. if (reparented != node) { DATA_PTR(node_obj) = reparented ; } // Appropriately link in namespaces relink_namespace(reparented); reparented_obj = Nokogiri_wrap_xml_node(Qnil, reparented); rb_funcall(reparented_obj, decorate_bang, 0); return reparented_obj ; }