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

[draft] (jruby) namespaced attributes #2679

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 15 additions & 5 deletions ext/java/nokogiri/XmlNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -644,12 +644,22 @@ public class XmlNode extends RubyObject

@JRubyMethod(name = {"attribute", "attr"})
public IRubyObject
attribute(ThreadContext context, IRubyObject name)
attribute(ThreadContext context, IRubyObject rbName)
{
NamedNodeMap attrs = this.node.getAttributes();
Node attr = attrs.getNamedItem(rubyStringToString(name));
if (attr == null) { return context.nil; }
return getCachedNodeOrCreate(context.runtime, attr);
NamedNodeMap attributes = this.node.getAttributes();
String name = rubyStringToString(rbName);

for (int j = 0 ; j < attributes.getLength() ; j++) {
Node attribute = attributes.item(j);
String localName = attribute.getLocalName();
if (localName == null) {
continue;
}
if (localName.equals(name)) {
return getCachedNodeOrCreate(context.runtime, attribute);
}
}
return context.nil;
}

@JRubyMethod
Expand Down
205 changes: 125 additions & 80 deletions test/xml/test_node_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,115 +2,160 @@

require "helper"

module Nokogiri
module XML
class TestNodeAttributes < Nokogiri::TestCase
def test_attribute_with_ns
doc = Nokogiri::XML(<<-eoxml)
<root xmlns:tlm='http://tenderlovemaking.com/'>
<node tlm:foo='bar' foo='baz' />
</root>
eoxml

node = doc.at("node")

assert_equal("bar",
node.attribute_with_ns("foo", "http://tenderlovemaking.com/").value)
describe Nokogiri::XML::Node do
describe "attributes" do
let(:simple_xml_with_namespaces) { <<~XML }
<root xmlns:tlm='http://tenderlovemaking.com/'>
<node tlm:foo='bar' foo='baz' />
<next tlm:foo='baz' />
</root>
XML

describe "#attribute" do
it "returns an attribute that matches the local name" do
doc = Nokogiri.XML(simple_xml_with_namespaces)
node = doc.at_css("next")
refute_nil(attr = node.attribute("foo"))

# NOTE: that we don't make any claim over _which_ attribute should be returned.
# this situation is ambiguous and we make no guarantees.
assert_equal("foo", attr.name)
end

def test_prefixed_attributes
doc = Nokogiri::XML("<root xml:lang='en-GB' />")
it "does not return an attribute that matches the full name" do
doc = Nokogiri.XML(simple_xml_with_namespaces)
node = doc.at_css("next")
assert_nil(node.attribute("tlm:foo"))
end
end

node = doc.root
describe "#attribute_with_ns" do
it "returns the attribute that matches the name and namespace" do
doc = Nokogiri.XML(simple_xml_with_namespaces)
node = doc.at_css("node")

assert_equal("en-GB", node["xml:lang"])
assert_equal("en-GB", node.attributes["lang"].value)
assert_nil(node["lang"])
refute_nil(attr = node.attribute_with_ns("foo", "http://tenderlovemaking.com/"))
assert_equal("bar", attr.value)
end

def test_unknown_namespace_prefix_should_not_be_removed
doc = Nokogiri::XML("")
elem = doc.create_element("foo", "bar:attr" => "something")
assert_equal("bar:attr", elem.attribute_nodes.first.name)
it "returns the attribute that matches the name and nil namespace" do
doc = Nokogiri.XML(simple_xml_with_namespaces)
node = doc.at_css("node")

refute_nil(attr = node.attribute_with_ns("foo", nil))
assert_equal("baz", attr.value)
end

def test_set_prefixed_attributes
doc = Nokogiri::XML(%{<root xmlns:foo="x"/>})
it "does not return a attribute that matches name but not namespace" do
doc = Nokogiri.XML(simple_xml_with_namespaces)
node = doc.at_css("node")

node = doc.root
assert_nil(node.attribute_with_ns("foo", "http://nokogiri.org/"))
end

node["xml:lang"] = "en-GB"
node["foo:bar"] = "bazz"
it "does not return a attribute that matches namespace but not name" do
doc = Nokogiri.XML(simple_xml_with_namespaces)
node = doc.at_css("node")

assert_equal("en-GB", node["xml:lang"])
assert_equal("en-GB", node.attributes["lang"].value)
assert_nil(node["lang"])
assert_equal("http://www.w3.org/XML/1998/namespace", node.attributes["lang"].namespace.href)
assert_nil(node.attribute_with_ns("not-present", "http://tenderlovemaking.com/"))
end
end

assert_equal("bazz", node["foo:bar"])
assert_equal("bazz", node.attributes["bar"].value)
assert_nil(node["bar"])
assert_equal("x", node.attributes["bar"].namespace.href)
describe "#set_attribute" do
it "round trips" do
doc = Nokogiri.XML(simple_xml_with_namespaces)
node = doc.at_css("node")
node["xxx"] = "yyy"
refute_nil(node.attribute("xxx"))
assert_equal("yyy", node.attribute("xxx").value)
end
end

def test_append_child_namespace_definitions_prefixed_attributes
doc = Nokogiri::XML("<root/>")
node = doc.root
it "prefixed_attributes" do
doc = Nokogiri.XML("<root xml:lang='en-GB' />")
node = doc.root

node["xml:lang"] = "en-GB"
assert_equal("en-GB", node["xml:lang"])
assert_equal("en-GB", node.attributes["lang"].value)
assert_nil(node["lang"])
end

assert_empty(node.namespace_definitions.map(&:prefix))
it "unknown_namespace_prefix_should_not_be_removed" do
doc = Nokogiri.XML("")
elem = doc.create_element("foo", "bar:attr" => "something")
assert_equal("bar:attr", elem.attribute_nodes.first.name)
end

child_node = Nokogiri::XML::Node.new("foo", doc)
node << child_node
it "set_prefixed_attributes" do
doc = Nokogiri.XML(%{<root xmlns:foo="x"/>})
node = doc.root

assert_empty(node.namespace_definitions.map(&:prefix))
end
node["xml:lang"] = "en-GB"
node["foo:bar"] = "bazz"

assert_equal("en-GB", node["xml:lang"])
assert_equal("en-GB", node.attributes["lang"].value)
assert_nil(node["lang"])
assert_equal("http://www.w3.org/XML/1998/namespace", node.attributes["lang"].namespace.href)

def test_append_child_element_with_prefixed_attributes
doc = Nokogiri::XML("<root/>")
node = doc.root
assert_equal("bazz", node["foo:bar"])
assert_equal("bazz", node.attributes["bar"].value)
assert_nil(node["bar"])
assert_equal("x", node.attributes["bar"].namespace.href)
end

assert_empty(node.namespace_definitions.map(&:prefix))
it "append_child_namespace_definitions_prefixed_attributes" do
doc = Nokogiri.XML("<root/>")
node = doc.root

# assert_nothing_raised do
child_node = Nokogiri::XML::Node.new("foo", doc)
child_node["xml:lang"] = "en-GB"
node["xml:lang"] = "en-GB"

node << child_node
# end
assert_empty(node.namespace_definitions.map(&:prefix))

assert_empty(child_node.namespace_definitions.map(&:prefix))
end
child_node = Nokogiri::XML::Node.new("foo", doc)
node << child_node

assert_empty(node.namespace_definitions.map(&:prefix))
end

def test_namespace_key?
doc = Nokogiri::XML(<<-eoxml)
<root xmlns:tlm='http://tenderlovemaking.com/'>
<node tlm:foo='bar' foo='baz' />
</root>
eoxml
it "append_child_element_with_prefixed_attributes" do
doc = Nokogiri.XML("<root/>")
node = doc.root

node = doc.at("node")
assert_empty(node.namespace_definitions.map(&:prefix))

assert(node.namespaced_key?("foo", "http://tenderlovemaking.com/"))
assert(node.namespaced_key?("foo", nil))
refute(node.namespaced_key?("foo", "foo"))
end
# assert_nothing_raised do
child_node = Nokogiri::XML::Node.new("foo", doc)
child_node["xml:lang"] = "en-GB"

node << child_node
# end

assert_empty(child_node.namespace_definitions.map(&:prefix))
end

it "namespace_key?" do
doc = Nokogiri.XML(simple_xml_with_namespaces)
node = doc.at_css("node")

assert(node.namespaced_key?("foo", "http://tenderlovemaking.com/"))
assert(node.namespaced_key?("foo", nil))
refute(node.namespaced_key?("foo", "foo"))
end

def test_set_attribute_frees_nodes
# testing a segv
skip_unless_libxml2("JRuby doesn't do GC.")
document = Nokogiri::XML.parse("<foo></foo>")
it "set_attribute_frees_nodes" do
# testing a segv
skip_unless_libxml2("JRuby doesn't do GC.")
document = Nokogiri::XML.parse("<foo></foo>")

node = document.root
node["visible"] = "foo"
attribute = node.attribute("visible")
text = Nokogiri::XML::Text.new("bar", document)
attribute.add_child(text)
node = document.root
node["visible"] = "foo"
attribute = node.attribute("visible")
text = Nokogiri::XML::Text.new("bar", document)
attribute.add_child(text)

stress_memory_while do
node["visible"] = "attr"
end
stress_memory_while do
node["visible"] = "attr"
end
end
end
Expand Down