Skip to content

Commit 12874a7

Browse files
committed
feat: Reader#attribute_hash
which is now being used to implement Reader#attributes fix: Reader#namespaces on JRuby now returns an empty hash when no attributes are present (previously returned nil)
1 parent 193a07d commit 12874a7

File tree

5 files changed

+101
-17
lines changed

5 files changed

+101
-17
lines changed

ext/java/nokogiri/XmlReader.java

+7
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ public class XmlReader extends RubyObject
144144
return currentNode().getAttributesNodes();
145145
}
146146

147+
@JRubyMethod
148+
public IRubyObject
149+
attribute_hash(ThreadContext context)
150+
{
151+
return currentNode().getAttributes(context);
152+
}
153+
147154
@JRubyMethod(name = "attributes?")
148155
public IRubyObject
149156
attributes_p(ThreadContext context)

ext/java/nokogiri/internals/ReaderNode.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,10 @@ public abstract class ReaderNode
112112
getAttributes(ThreadContext context)
113113
{
114114
final Ruby runtime = context.runtime;
115-
if (attributeList == null) { return runtime.getNil(); }
116115
RubyHash hash = RubyHash.newHash(runtime);
116+
if (attributeList == null) { return hash; }
117117
for (int i = 0; i < attributeList.length; i++) {
118+
if (isNamespace(attributeList.names.get(i))) { continue; }
118119
IRubyObject k = stringOrBlank(runtime, attributeList.names.get(i));
119120
IRubyObject v = stringOrBlank(runtime, attributeList.values.get(i));
120121
hash.fastASetCheckString(runtime, k, v); // hash.op_aset(context, k, v)
@@ -150,8 +151,8 @@ public abstract class ReaderNode
150151
getNamespaces(ThreadContext context)
151152
{
152153
final Ruby runtime = context.runtime;
153-
if (namespaces == null) { return runtime.getNil(); }
154154
RubyHash hash = RubyHash.newHash(runtime);
155+
if (namespaces == null) { return hash; }
155156
for (Map.Entry<String, String> entry : namespaces.entrySet()) {
156157
IRubyObject k = stringOrBlank(runtime, entry.getKey());
157158
IRubyObject v = stringOrBlank(runtime, entry.getValue());

ext/nokogiri/xml_reader.c

+43
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ has_attributes(xmlTextReaderPtr reader)
3131
return (0);
3232
}
3333

34+
// TODO: merge this function into the `namespaces` method implementation
3435
static void
3536
Nokogiri_xml_node_namespaces(xmlNodePtr node, VALUE attr_hash)
3637
{
@@ -181,6 +182,47 @@ rb_xml_reader_attribute_nodes(VALUE rb_reader)
181182
return attr_nodes;
182183
}
183184

185+
/*
186+
:call-seq: attribute_hash() → Hash<String ⇒ String>
187+
188+
Get the attributes of the current node as a Hash of names and values.
189+
190+
See related: #attributes and #namespaces
191+
*/
192+
static VALUE
193+
rb_xml_reader_attribute_hash(VALUE rb_reader)
194+
{
195+
VALUE rb_attributes = rb_hash_new();
196+
xmlTextReaderPtr c_reader;
197+
xmlNodePtr c_node;
198+
xmlAttrPtr c_property;
199+
200+
Data_Get_Struct(rb_reader, xmlTextReader, c_reader);
201+
202+
if (!has_attributes(c_reader)) {
203+
return rb_attributes;
204+
}
205+
206+
c_node = xmlTextReaderExpand(c_reader);
207+
c_property = c_node->properties;
208+
while (c_property != NULL) {
209+
VALUE rb_name = NOKOGIRI_STR_NEW2(c_property->name);
210+
VALUE rb_value = Qnil;
211+
xmlChar *c_value = xmlNodeGetContent((xmlNode *)c_property);
212+
213+
if (c_value) {
214+
rb_value = NOKOGIRI_STR_NEW2(c_value);
215+
xmlFree(c_value);
216+
}
217+
218+
rb_hash_aset(rb_attributes, rb_name, rb_value);
219+
220+
c_property = c_property->next;
221+
}
222+
223+
return rb_attributes;
224+
}
225+
184226
/*
185227
* call-seq:
186228
* attribute_at(index)
@@ -696,6 +738,7 @@ noko_init_xml_reader()
696738
rb_define_method(cNokogiriXmlReader, "attribute_at", attribute_at, 1);
697739
rb_define_method(cNokogiriXmlReader, "attribute_count", attribute_count, 0);
698740
rb_define_method(cNokogiriXmlReader, "attribute_nodes", rb_xml_reader_attribute_nodes, 0);
741+
rb_define_method(cNokogiriXmlReader, "attribute_hash", rb_xml_reader_attribute_hash, 0);
699742
rb_define_method(cNokogiriXmlReader, "attributes?", attributes_eh, 0);
700743
rb_define_method(cNokogiriXmlReader, "base_uri", rb_xml_reader_base_uri, 0);
701744
rb_define_method(cNokogiriXmlReader, "default?", default_eh, 0);

lib/nokogiri/xml/reader.rb

+1-6
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,7 @@ def initialize(source, url = nil, encoding = nil) # :nodoc:
8787
#
8888
# [Returns] (Hash<String, String>) Attribute names and values
8989
def attributes
90-
attrs_hash = attribute_nodes.each_with_object({}) do |node, hash|
91-
hash[node.name] = node.to_s
92-
end
93-
ns = namespaces
94-
attrs_hash.merge!(ns) if ns
95-
attrs_hash
90+
attribute_hash.merge(namespaces)
9691
end
9792

9893
###

test/xml/test_reader.rb

+47-9
Original file line numberDiff line numberDiff line change
@@ -228,23 +228,61 @@ def test_attributes?
228228
reader.map(&:attributes?))
229229
end
230230

231-
def test_attributes
232-
reader = Nokogiri::XML::Reader.from_memory(<<-eoxml)
233-
<x xmlns:tenderlove='http://tenderlovemaking.com/'
234-
xmlns='http://mothership.connection.com/'
235-
>
236-
<tenderlove:foo awesome='true'>snuggles!</tenderlove:foo>
237-
</x>
238-
eoxml
231+
def test_reader_attributes
232+
reader = Nokogiri::XML::Reader.from_memory(<<~XML)
233+
<x xmlns:tenderlove='http://tenderlovemaking.com/' xmlns='http://mothership.connection.com/'>
234+
<tenderlove:foo awesome='true'>snuggles!</tenderlove:foo>
235+
</x>
236+
XML
239237
assert_empty(reader.attributes)
240238
assert_equal([{ "xmlns:tenderlove" => "http://tenderlovemaking.com/",
241239
"xmlns" => "http://mothership.connection.com/", },
242-
{}, { "awesome" => "true" }, {}, { "awesome" => "true" }, {},
240+
{},
241+
{ "awesome" => "true" },
242+
{},
243+
{ "awesome" => "true" },
244+
{},
243245
{ "xmlns:tenderlove" => "http://tenderlovemaking.com/",
244246
"xmlns" => "http://mothership.connection.com/", },],
245247
reader.map(&:attributes))
246248
end
247249

250+
def test_reader_attributes_hash
251+
reader = Nokogiri::XML::Reader.from_memory(<<~XML)
252+
<x xmlns:tenderlove='http://tenderlovemaking.com/' xmlns='http://mothership.connection.com/'>
253+
<tenderlove:foo awesome='true'>snuggles!</tenderlove:foo>
254+
</x>
255+
XML
256+
assert_empty(reader.attribute_hash)
257+
assert_equal([{},
258+
{},
259+
{ "awesome" => "true" },
260+
{},
261+
{ "awesome" => "true" },
262+
{},
263+
{},],
264+
reader.map(&:attribute_hash))
265+
end
266+
267+
def test_reader_namespaces
268+
reader = Nokogiri::XML::Reader.from_memory(<<~XML)
269+
<x xmlns:tenderlove='http://tenderlovemaking.com/' xmlns='http://mothership.connection.com/'>
270+
<tenderlove:foo awesome='true'>snuggles!</tenderlove:foo>
271+
</x>
272+
XML
273+
assert_empty(reader.namespaces)
274+
assert_equal([{ "xmlns:tenderlove" => "http://tenderlovemaking.com/",
275+
"xmlns" => "http://mothership.connection.com/", },
276+
{},
277+
{},
278+
{},
279+
{},
280+
{},
281+
{ "xmlns:tenderlove" => "http://tenderlovemaking.com/",
282+
"xmlns" => "http://mothership.connection.com/", },],
283+
reader.map(&:namespaces))
284+
end
285+
248286
def test_attribute_roundtrip
249287
reader = Nokogiri::XML::Reader.from_memory(<<-eoxml)
250288
<x xmlns:tenderlove='http://tenderlovemaking.com/'

0 commit comments

Comments
 (0)