/* * call-seq: * unlink * * Unlink this node from its current context. */ static VALUE unlink_node(VALUE self) { xmlNodePtr node; Data_Get_Struct(self, xmlNode, node); xmlUnlinkNode(node); NOKOGIRI_ROOT_NODE(node); return self; }
/* :nodoc: */ static VALUE replace(VALUE self, VALUE new_node) { VALUE reparent = reparent_node_with(self, new_node, xmlReplaceNodeWrapper); xmlNodePtr pivot; Data_Get_Struct(self, xmlNode, pivot); NOKOGIRI_ROOT_NODE(pivot); return reparent; }
/* * call-seq: * root= * * Set the root element on this document */ static VALUE set_root(VALUE self, VALUE root) { xmlDocPtr doc; xmlNodePtr new_root; Data_Get_Struct(self, xmlDoc, doc); xmlNodePtr old_root = NULL; if(NIL_P(root)) { old_root = xmlDocGetRootElement(doc); if(old_root) { xmlUnlinkNode(old_root); NOKOGIRI_ROOT_NODE(old_root); } return root; } Data_Get_Struct(root, xmlNode, new_root); /* If the new root's document is not the same as the current document, * then we need to dup the node in to this document. */ if(new_root->doc != doc) { old_root = xmlDocGetRootElement(doc); if (!(new_root = xmlDocCopyNode(new_root, doc, 1))) { rb_raise(rb_eRuntimeError, "Could not reparent node (xmlDocCopyNode)"); } } xmlDocSetRootElement(doc, new_root); if(old_root) NOKOGIRI_ROOT_NODE(old_root); return root; }
/* * call-seq: * dup * * Copy this node. An optional depth may be passed in, but it defaults * to a deep copy. 0 is a shallow copy, 1 is a deep copy. */ static VALUE duplicate_node(int argc, VALUE *argv, VALUE self) { VALUE level; xmlNodePtr node, dup; if(rb_scan_args(argc, argv, "01", &level) == 0) level = INT2NUM((long)1); Data_Get_Struct(self, xmlNode, node); dup = xmlDocCopyNode(node, node->doc, (int)NUM2INT(level)); if(dup == NULL) return Qnil; NOKOGIRI_ROOT_NODE(dup); return Nokogiri_wrap_xml_node(rb_obj_class(self), dup); }
/* :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); } 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 ; }