Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix namespace reparenting issue #2494. #2495

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions ext/nokogiri/xml_node.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,16 @@ static const rb_data_type_t nokogiri_node_type = {
};

static void
relink_namespace(xmlNodePtr reparented)
relink_namespace(xmlNodePtr reparented, int reconcile_namespaces)
{
xmlNodePtr child;
xmlAttrPtr attr;
xmlNsPtr possible_collision_ns;
int ns_collision;

if (reconcile_namespaces && reparented->doc) {
xmlReconciliateNs(reparented->doc, reparented);
}

if (reparented->type != XML_ATTRIBUTE_NODE &&
reparented->type != XML_ELEMENT_NODE) { return; }
Expand Down Expand Up @@ -118,9 +124,16 @@ relink_namespace(xmlNodePtr reparented)
reparented->parent,
curr->href
);
/* Track and check for a namespace which might be 'squatting' on a
* the same prefix but a different href. */
ns_collision = 0;
possible_collision_ns = xmlSearchNs(reparented->doc, reparented->parent, curr->prefix);
if (possible_collision_ns && !xmlStrEqual(curr->href, possible_collision_ns->href)) {
ns_collision = 1;
}
/* If we find the namespace is already declared, remove it from this
* definition list. */
if (ns && ns != curr && xmlStrEqual(ns->prefix, curr->prefix)) {
if (ns && ns != curr && !ns_collision && xmlStrEqual(ns->prefix, curr->prefix)) {
if (prev) {
prev->next = curr->next;
} else {
Expand Down Expand Up @@ -161,14 +174,14 @@ relink_namespace(xmlNodePtr reparented)
/* their namespaces are reparented as well. */
child = reparented->children;
while (NULL != child) {
relink_namespace(child);
relink_namespace(child, 0);
child = child->next;
}

if (reparented->type == XML_ELEMENT_NODE) {
attr = reparented->properties;
while (NULL != attr) {
relink_namespace((xmlNodePtr)attr);
relink_namespace((xmlNodePtr)attr, 0);
attr = attr->next;
}
}
Expand Down Expand Up @@ -218,6 +231,7 @@ reparent_node_with(VALUE pivot_obj, VALUE reparentee_obj, pivot_reparentee_func
{
VALUE reparented_obj ;
xmlNodePtr reparentee, original_reparentee, pivot, reparented, next_text, new_next_text, parent ;
int reconcile_ns = 1;
int original_ns_prefix_is_default = 0 ;

if (!rb_obj_is_kind_of(reparentee_obj, cNokogiriXmlNode)) {
Expand All @@ -227,6 +241,11 @@ reparent_node_with(VALUE pivot_obj, VALUE reparentee_obj, pivot_reparentee_func
rb_raise(rb_eArgError, "node must be a Nokogiri::XML::Node");
}

// Don't reconcile children of fragments.
if (rb_obj_is_kind_of(pivot_obj, cNokogiriXmlDocumentFragment)) {
reconcile_ns = 0;
}

Noko_Node_Get_Struct(reparentee_obj, xmlNode, reparentee);
Noko_Node_Get_Struct(pivot_obj, xmlNode, pivot);

Expand Down Expand Up @@ -408,7 +427,7 @@ reparent_node_with(VALUE pivot_obj, VALUE reparentee_obj, pivot_reparentee_func
/* if we've created a cycle, raise an exception */
raise_if_ancestor_of_self(reparented);

relink_namespace(reparented);
relink_namespace(reparented, reconcile_ns);

return reparented_obj ;
}
Expand Down
22 changes: 22 additions & 0 deletions test/xml/test_namespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,28 @@ def test_maintain_element_namespaces_in_urn
assert_equal("urn:xmpp:foospec:barfoo", child.namespace.href)
assert_empty(child.attributes)
end

def test_maintain_element_namespaces_with_abbreviation_squating
root_namespace_href = "urn:root_namespace"
child1_namespace_href = "urn:child1_namespace"
doc = Nokogiri::XML(<<-eoxml)
<root xmlns="#{root_namespace_href}">
<child1 xmlns="#{child1_namespace_href}"/>
<child2/>
</root>
eoxml

child2 = doc.at_xpath("//ns1:child2", { "ns1" => root_namespace_href })
child1 = doc.at_xpath("//ns2:child1", { "ns2" => child1_namespace_href })
child1.add_child(child2)
new_xml = doc.to_xml

new_doc = Nokogiri::XML(new_xml)
new_child1 = new_doc.at_xpath("//ns2:child1", { "ns2" => child1_namespace_href })
new_child2 = new_child1.first_element_child
new_child2_ns_href = new_child2.namespace.href
assert_equal(root_namespace_href, new_child2_ns_href)
end
end
end
end