Skip to content

Commit

Permalink
feat/fix: introduce Document#namespace_inheritance attr
Browse files Browse the repository at this point in the history
When true, reparented elements without a namespace will inherit their
new parent's namespace (if one exists). Defaults to +false+.

This is part of addressing the behavior change introduced in v1.12 by
1f483f0, allowing us to switch it on or off per-document. See #2317.
  • Loading branch information
flavorjones committed Aug 28, 2021
1 parent e3f2209 commit 4c0bc81
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 26 deletions.
12 changes: 0 additions & 12 deletions ext/java/nokogiri/XmlDocumentFragment.java
Expand Up @@ -34,8 +34,6 @@
public class XmlDocumentFragment extends XmlNode
{

private XmlElement fragmentContext;

public
XmlDocumentFragment(Ruby ruby)
{
Expand Down Expand Up @@ -75,10 +73,6 @@ public class XmlDocumentFragment extends XmlNode
fragment.setDocument(context, doc);
fragment.setNode(context.runtime, doc.getDocument().createDocumentFragment());

//TODO: Get namespace definitions from doc.
if (args.length == 3 && args[2] != null && args[2] instanceof XmlElement) {
fragment.fragmentContext = (XmlElement)args[2];
}
Helpers.invoke(context, fragment, "initialize", args);
return fragment;
}
Expand Down Expand Up @@ -158,12 +152,6 @@ public class XmlDocumentFragment extends XmlNode
return null;
}

public XmlElement
getFragmentContext()
{
return fragmentContext;
}

@Override
public void
relink_namespace(ThreadContext context)
Expand Down
23 changes: 9 additions & 14 deletions ext/java/nokogiri/XmlNode.java
Expand Up @@ -12,6 +12,7 @@
import org.apache.xerces.dom.CoreDocumentImpl;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyInteger;
Expand Down Expand Up @@ -421,7 +422,14 @@ public class XmlNode extends RubyObject
String nsURI = e.lookupNamespaceURI(prefix);
this.node = NokogiriHelpers.renameNode(e, nsURI, e.getNodeName());

if (nsURI == null || nsURI.isEmpty()) { return; }
if (nsURI == null || nsURI.isEmpty()) {
RubyBoolean ns_inherit =
(RubyBoolean)document(context.runtime).getInstanceVariable("@namespace_inheritance");
if (ns_inherit.isTrue()) {
set_namespace(context, ((XmlNode)parent(context)).namespace(context));
}
return;
}

String currentPrefix = e.getParentNode().lookupPrefix(nsURI);
String currentURI = e.getParentNode().lookupNamespaceURI(prefix);
Expand Down Expand Up @@ -1743,24 +1751,11 @@ protected enum AdoptScheme {
e.appendChild(otherNode);
otherNode = e;
} else {
addNamespaceURIIfNeeded(otherNode);
parent.appendChild(otherNode);
}
return new Node[] { otherNode };
}

private void
addNamespaceURIIfNeeded(Node child)
{
if (this instanceof XmlDocumentFragment && ((XmlDocumentFragment) this).getFragmentContext() != null) {
XmlElement fragmentContext = ((XmlDocumentFragment) this).getFragmentContext();
String namespace_uri = fragmentContext.node.getNamespaceURI();
if (namespace_uri != null && namespace_uri.length() > 0) {
NokogiriHelpers.renameNode(child, namespace_uri, child.getNodeName());
}
}
}

protected void
adoptAsPrevSibling(ThreadContext context,
Node parent,
Expand Down
7 changes: 7 additions & 0 deletions ext/nokogiri/xml_node.c
Expand Up @@ -69,6 +69,13 @@ relink_namespace(xmlNodePtr reparented)
/* Avoid segv when relinking against unlinked nodes. */
if (reparented->type != XML_ELEMENT_NODE || !reparented->parent) { return; }

/* Make sure that our reparented node has the correct namespaces */
if (!reparented->ns &&
(reparented->doc != (xmlDocPtr)reparented->parent) &&
(rb_iv_get(DOC_RUBY_OBJECT(reparented->doc), "@namespace_inheritance") == Qtrue)) {
xmlSetNs(reparented, reparented->parent->ns);
}

/* Search our parents for an existing definition */
if (reparented->nsDef) {
xmlNsPtr curr = reparented->nsDef;
Expand Down
46 changes: 46 additions & 0 deletions lib/nokogiri/xml/document.rb
Expand Up @@ -113,9 +113,55 @@ def self.parse string_or_io, url = nil, encoding = nil, options = ParseOptions::
# A list of Nokogiri::XML::SyntaxError found when parsing a document
attr_accessor :errors

# When true, reparented elements without a namespace will inherit their new parent's
# namespace (if one exists). Defaults to +false+.
#
# @example Default behavior of namespace inheritance
# xml = <<~EOF
# <root xmlns:foo="http://nokogiri.org/default_ns/test/foo">
# <foo:parent>
# </foo:parent>
# </root>
# EOF
# doc = Nokogiri::XML(xml)
# parent = doc.at_xpath("//foo:parent", "foo" => "http://nokogiri.org/default_ns/test/foo")
# parent.add_child("<child></child>")
# doc.to_xml
# # => <?xml version="1.0"?>
# # <root xmlns:foo="http://nokogiri.org/default_ns/test/foo">
# # <foo:parent>
# # <child/>
# # </foo:parent>
# # </root>
#
# @example Setting namespace inheritance to +true+
# xml = <<~EOF
# <root xmlns:foo="http://nokogiri.org/default_ns/test/foo">
# <foo:parent>
# </foo:parent>
# </root>
# EOF
# doc = Nokogiri::XML(xml)
# doc.namespace_inheritance = true
# parent = doc.at_xpath("//foo:parent", "foo" => "http://nokogiri.org/default_ns/test/foo")
# parent.add_child("<child></child>")
# doc.to_xml
# # => <?xml version="1.0"?>
# # <root xmlns:foo="http://nokogiri.org/default_ns/test/foo">
# # <foo:parent>
# # <foo:child/>
# # </foo:parent>
# # </root>
#
# @return [Boolean]
#
# @since v1.12.4
attr_accessor :namespace_inheritance

def initialize *args # :nodoc:
@errors = []
@decorators = nil
@namespace_inheritance = false
end

##
Expand Down
7 changes: 7 additions & 0 deletions test/xml/test_document.rb
Expand Up @@ -15,6 +15,13 @@ class TestDocument < Nokogiri::TestCase

let(:xml) { Nokogiri::XML.parse(File.read(XML_FILE), XML_FILE) }

def test_default_namespace_inheritance
doc = Nokogiri::XML::Document.new
refute(doc.namespace_inheritance)
doc.namespace_inheritance = true
assert(doc.namespace_inheritance)
end

def test_dtd_with_empty_internal_subset
doc = Nokogiri::XML(<<~eoxml)
<?xml version="1.0"?>
Expand Down
31 changes: 31 additions & 0 deletions test/xml/test_node_reparenting.rb
Expand Up @@ -557,6 +557,37 @@ def coerce(data)
end
end
end

describe "given a parent node with a non-default namespace" do
let(:doc) do
Nokogiri::XML(<<~EOF)
<root xmlns:foo="http://nokogiri.org/default_ns/test/foo">
<foo:parent>
</foo:parent>
</root>
EOF
end
let(:parent) { doc.at_xpath("//foo:parent", "foo" => "http://nokogiri.org/default_ns/test/foo") }

describe "and namespace_inheritance is off" do
it "inserts a child node that does not inherit the parent's namespace" do
refute(doc.namespace_inheritance)
child = parent.add_child("<child></child>").first
puts doc.to_xml
puts child.namespace.inspect
assert_nil(child.namespace)
end
end

describe "and namespace_inheritance is on" do
it "inserts a child node that inherits the parent's namespace" do
doc.namespace_inheritance = true
child = parent.add_child("<child></child>").first
assert_not_nil(child.namespace)
assert_equal("http://nokogiri.org/default_ns/test/foo", child.namespace.href)
end
end
end
end

describe "#add_previous_sibling" do
Expand Down

0 comments on commit 4c0bc81

Please sign in to comment.