/* * call-seq: * dup * dup(depth) * dup(depth, new_parent_doc) * * Copy this node. * An optional depth may be passed in. 0 is a shallow copy, 1 (the default) is a deep copy. * An optional new_parent_doc may also be passed in, which will be the new * node's parent document. Defaults to the current node's document. * current document. */ static VALUE duplicate_node(int argc, VALUE *argv, VALUE self) { VALUE r_level, r_new_parent_doc; int level; int n_args; xmlDocPtr new_parent_doc; xmlNodePtr node, dup; Data_Get_Struct(self, xmlNode, node); n_args = rb_scan_args(argc, argv, "02", &r_level, &r_new_parent_doc); if (n_args < 1) { r_level = INT2NUM((long)1); } level = (int)NUM2INT(r_level); if (n_args < 2) { new_parent_doc = node->doc; } else { Data_Get_Struct(r_new_parent_doc, xmlDoc, new_parent_doc); } dup = xmlDocCopyNode(node, new_parent_doc, level); if(dup == NULL) { return Qnil; } nokogiri_root_node(dup); return Nokogiri_wrap_xml_node(rb_obj_class(self), dup); }
/* * call-seq: * create_external_subset(name, external_id, system_id) * * Create an external subset */ static VALUE create_external_subset(VALUE self, VALUE name, VALUE external_id, VALUE system_id) { xmlNodePtr node; xmlDocPtr doc; xmlDtdPtr dtd; Data_Get_Struct(self, xmlNode, node); doc = node->doc; if(doc->extSubset) { rb_raise(rb_eRuntimeError, "Document already has an external subset"); } dtd = xmlNewDtd( doc, NIL_P(name) ? NULL : (const xmlChar *)StringValueCStr(name), NIL_P(external_id) ? NULL : (const xmlChar *)StringValueCStr(external_id), NIL_P(system_id) ? NULL : (const xmlChar *)StringValueCStr(system_id) ); if(!dtd) { return Qnil; } return Nokogiri_wrap_xml_node(Qnil, (xmlNodePtr)dtd); }
static void element_copier(void *_payload, void *data, xmlChar *name) { VALUE hash = (VALUE)data; xmlNodePtr payload = (xmlNodePtr)_payload; VALUE element = Nokogiri_wrap_xml_node(Qnil, payload); rb_hash_aset(hash, NOKOGIRI_STR_NEW2(name), element); }
/* * call-seq: * root * * Get the root node for this document. */ static VALUE root(VALUE self) { xmlDocPtr doc; Data_Get_Struct(self, xmlDoc, doc); xmlNodePtr root = xmlDocGetRootElement(doc); if(!root) return Qnil; return Nokogiri_wrap_xml_node(Qnil, root) ; }
/* * call-seq: * previous_sibling * * Returns the previous sibling node */ static VALUE previous_sibling(VALUE self) { xmlNodePtr node, sibling; Data_Get_Struct(self, xmlNode, node); sibling = node->prev; if(!sibling) return Qnil; return Nokogiri_wrap_xml_node(Qnil, sibling); }
/* * call-seq: * attribute(name) * * Get the attribute node with +name+ */ static VALUE attr(VALUE self, VALUE name) { xmlNodePtr node; xmlAttrPtr prop; Data_Get_Struct(self, xmlNode, node); prop = xmlHasProp(node, (xmlChar *)StringValuePtr(name)); if(! prop) return Qnil; return Nokogiri_wrap_xml_node((xmlNodePtr)prop); }
/* * call-seq: * next_sibling * * Returns the next sibling node */ static VALUE next_sibling(VALUE self) { xmlNodePtr node, sibling; Data_Get_Struct(self, xmlNode, node); sibling = node->next; if(!sibling) return Qnil; return Nokogiri_wrap_xml_node(sibling) ; }
/* * call-seq: * parent * * Get the parent Node for this Node */ static VALUE get_parent(VALUE self) { xmlNodePtr node, parent; Data_Get_Struct(self, xmlNode, node); parent = node->parent; if(!parent) return Qnil; return Nokogiri_wrap_xml_node(parent) ; }
/* * call-seq: * next_element * * Returns the next Nokogiri::XML::Element type sibling node. */ static VALUE next_element(VALUE self) { xmlNodePtr node, sibling; Data_Get_Struct(self, xmlNode, node); sibling = xmlNextElementSibling(node); if(!sibling) return Qnil; return Nokogiri_wrap_xml_node(Qnil, sibling); }
/* * call-seq: * child * * Returns the child node */ static VALUE child(VALUE self) { xmlNodePtr node, child; Data_Get_Struct(self, xmlNode, node); child = node->children; if(!child) return Qnil; return Nokogiri_wrap_xml_node(Qnil, child); }
/* * call-seq: * last_element_child * * Returns the last child node of this node that is an element. * * Example: * * @doc.root.last_element_child.element? # => true */ static VALUE last_element_child(VALUE self) { xmlNodePtr node, child; Data_Get_Struct(self, xmlNode, node); child = xmlLastElementChild(node); if(!child) return Qnil; return Nokogiri_wrap_xml_node(Qnil, child); }
/* * call-seq: * previous_element * * Returns the previous Nokogiri::XML::Element type sibling node. */ static VALUE previous_element(VALUE self) { xmlNodePtr node, sibling; Data_Get_Struct(self, xmlNode, node); sibling = node->prev; if(!sibling) return Qnil; while(sibling && sibling->type != XML_ELEMENT_NODE) sibling = sibling->prev; return sibling ? Nokogiri_wrap_xml_node(Qnil, sibling) : Qnil ; }
/* * call-seq: * dup * * Copy this node */ static VALUE duplicate_node(VALUE self) { xmlNodePtr node, dup; Data_Get_Struct(self, xmlNode, node); dup = xmlCopyNode(node, 1); if(dup == NULL) return Qnil; dup->doc = node->doc; assert(node->parent); xmlAddChild(node->parent, dup); return Nokogiri_wrap_xml_node(dup); }
/* * call-seq: * internal_subset * * Get the internal subset */ static VALUE internal_subset(VALUE self) { xmlNodePtr node; xmlDocPtr doc; Data_Get_Struct(self, xmlNode, node); if(!node->doc) return Qnil; doc = node->doc; if(!doc->intSubset) return Qnil; return Nokogiri_wrap_xml_node((xmlNodePtr)doc->intSubset); }
/* * 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; if(rb_scan_args(argc, argv, "01", &level) == 0) level = INT2NUM(1); xmlNodePtr node, dup; Data_Get_Struct(self, xmlNode, node); dup = xmlDocCopyNode(node, node->doc, NUM2INT(level)); if(dup == NULL) return Qnil; return Nokogiri_wrap_xml_node(dup); }
/* * call-seq: * internal_subset * * Get the internal subset */ static VALUE internal_subset(VALUE self) { xmlNodePtr node; xmlDocPtr doc; Data_Get_Struct(self, xmlNode, node); if(!node->doc) return Qnil; doc = node->doc; xmlDtdPtr dtd = xmlGetIntSubset(doc); if(!dtd) return Qnil; return Nokogiri_wrap_xml_node(Qnil, (xmlNodePtr)dtd); }
/* * call-seq: * previous_element * * Returns the previous Nokogiri::XML::Element type sibling node. */ static VALUE previous_element(VALUE self) { xmlNodePtr node, sibling; Data_Get_Struct(self, xmlNode, node); /* * note that we don't use xmlPreviousElementSibling here because it's buggy pre-2.7.7. */ sibling = node->prev; if(!sibling) return Qnil; while(sibling && sibling->type != XML_ELEMENT_NODE) sibling = sibling->prev; return sibling ? Nokogiri_wrap_xml_node(Qnil, sibling) : Qnil ; }
/* * call-seq: * external_subset * * Get the external subset */ static VALUE external_subset(VALUE self) { xmlNodePtr node; xmlDocPtr doc; xmlDtdPtr dtd; Data_Get_Struct(self, xmlNode, node); if(!node->doc) { return Qnil; } doc = node->doc; dtd = doc->extSubset; if(!dtd) { return Qnil; } return Nokogiri_wrap_xml_node(Qnil, (xmlNodePtr)dtd); }
/* * 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 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 ; }
/* :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 (prf != xmlAddPrevSibling && prf != xmlAddNextSibling && 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 pivot_obj, VALUE reparentee_obj, pivot_reparentee_func prf) { VALUE reparented_obj ; xmlNodePtr reparentee, pivot, reparented, next_text, new_next_text, parent ; 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); /* * Check if nodes given are appropriate to have a parent-child * relationship, based on the DOM specification. * * cf. http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-1590626202 */ if (prf == xmlAddChild) { parent = pivot; } else { parent = pivot->parent; } if (parent) { switch (parent->type) { case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: switch (reparentee->type) { case XML_ELEMENT_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: case XML_DOCUMENT_TYPE_NODE: /* * The DOM specification says no to adding text-like nodes * directly to a document, but we allow it for compatibility. */ case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: case XML_ENTITY_REF_NODE: goto ok; } break; case XML_DOCUMENT_FRAG_NODE: case XML_ENTITY_REF_NODE: case XML_ELEMENT_NODE: switch (reparentee->type) { case XML_ELEMENT_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: case XML_ENTITY_REF_NODE: goto ok; } break; case XML_ATTRIBUTE_NODE: switch (reparentee->type) { case XML_TEXT_NODE: case XML_ENTITY_REF_NODE: goto ok; } break; case XML_TEXT_NODE: /* * xmlAddChild() breaks the DOM specification in that it allows * adding a text node to another, in which case text nodes are * coalesced, but since our JRuby version does not support such * operation, we should inhibit it. */ break; } rb_raise(rb_eArgError, "cannot reparent %s there", rb_obj_classname(reparentee_obj)); } ok: 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 (prf != xmlAddPrevSibling && prf != xmlAddNextSibling && 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 ; }
*/ static VALUE new(int argc, VALUE *argv, VALUE klass) { xmlDocPtr doc; VALUE string; VALUE document; VALUE rest; rb_scan_args(argc, argv, "2*", &string, &document, &rest); Data_Get_Struct(document, xmlDoc, doc); xmlNodePtr node = xmlNewText((xmlChar *)StringValuePtr(string)); node->doc = doc; VALUE rb_node = Nokogiri_wrap_xml_node(klass, node) ; rb_obj_call_init(rb_node, argc, argv); if(rb_block_given_p()) rb_yield(rb_node); return rb_node; } VALUE cNokogiriXmlText ; void init_xml_text() { VALUE nokogiri = rb_define_module("Nokogiri"); VALUE xml = rb_define_module_under(nokogiri, "XML"); /* */ VALUE node = rb_define_class_under(xml, "Node", rb_cObject); VALUE char_data = rb_define_class_under(xml, "CharacterData", node);
xmlAttrPtr node; VALUE rb_node; rb_scan_args(argc, argv, "2*", &document, &name, &rest); Data_Get_Struct(document, xmlDoc, xml_doc); node = xmlNewDocProp( xml_doc, (const xmlChar *)StringValuePtr(name), NULL ); NOKOGIRI_ROOT_NODE((xmlNodePtr)node); rb_node = Nokogiri_wrap_xml_node(klass, (xmlNodePtr)node); rb_obj_call_init(rb_node, argc, argv); if(rb_block_given_p()) rb_yield(rb_node); return rb_node; } VALUE cNokogiriXmlAttr; void init_xml_attr() { VALUE nokogiri = rb_define_module("Nokogiri"); VALUE xml = rb_define_module_under(nokogiri, "XML"); VALUE node = rb_define_class_under(xml, "Node", rb_cObject); /*
* new(document, content) * * Create a new CData element on the +document+ with +content+ */ static VALUE new(VALUE klass, VALUE doc, VALUE content) { xmlDocPtr xml_doc; Data_Get_Struct(doc, xmlDoc, xml_doc); xmlNodePtr node = xmlNewCDataBlock( xml_doc, (const xmlChar *)StringValuePtr(content), RSTRING_LEN(content) ); VALUE rb_node = Nokogiri_wrap_xml_node(node); if(rb_block_given_p()) rb_yield(rb_node); return rb_node; } VALUE cNokogiriXmlCData; void init_xml_cdata() { VALUE nokogiri = rb_define_module("Nokogiri"); VALUE xml = rb_define_module_under(nokogiri, "XML"); VALUE node = rb_define_class_under(xml, "Node", rb_cObject); VALUE text = rb_define_class_under(xml, "Text", node); /*