diff --git a/benchmark/each_recursive.yaml b/benchmark/each_recursive.yaml
new file mode 100644
index 00000000..c745f8ce
--- /dev/null
+++ b/benchmark/each_recursive.yaml
@@ -0,0 +1,40 @@
+loop_count: 100
+contexts:
+ - gems:
+ rexml: 3.2.6
+ require: false
+ prelude: require 'rexml'
+ - name: master
+ prelude: |
+ $LOAD_PATH.unshift(File.expand_path("lib"))
+ require 'rexml'
+ - name: 3.2.6(YJIT)
+ gems:
+ rexml: 3.2.6
+ require: false
+ prelude: |
+ require 'rexml'
+ RubyVM::YJIT.enable
+ - name: master(YJIT)
+ prelude: |
+ $LOAD_PATH.unshift(File.expand_path("lib"))
+ require 'rexml'
+ RubyVM::YJIT.enable
+
+prelude: |
+ require 'rexml/document'
+
+ xml_source = +""
+ 100.times do
+ x_node_source = ""
+ 100.times do
+ x_node_source = "#{x_node_source}"
+ end
+ xml_source << x_node_source
+ end
+ xml_source << ""
+
+ document = REXML::Document.new(xml_source)
+
+benchmark:
+ each_recursive: document.each_recursive { |_| }
diff --git a/lib/rexml/node.rb b/lib/rexml/node.rb
index 081caba6..c771db70 100644
--- a/lib/rexml/node.rb
+++ b/lib/rexml/node.rb
@@ -52,10 +52,14 @@ def parent?
# Visit all subnodes of +self+ recursively
def each_recursive(&block) # :yields: node
- self.elements.each {|node|
- block.call(node)
- node.each_recursive(&block)
- }
+ stack = []
+ each { |child| stack.unshift child if child.node_type == :element }
+ until stack.empty?
+ child = stack.pop
+ yield child
+ n = stack.size
+ child.each { |grandchild| stack.insert n, grandchild if grandchild.node_type == :element }
+ end
end
# Find (and return) first subnode (recursively) for which the block
diff --git a/test/test_document.rb b/test/test_document.rb
index 4bf3f55d..7fccbacb 100644
--- a/test/test_document.rb
+++ b/test/test_document.rb
@@ -209,6 +209,42 @@ def test_gt_linear_performance_attribute_value
end
end
+ def test_each_recursive
+ xml_source = <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+
+ expected_names = %w[
+ root
+ 1_1 1_2 1_3
+ 2_1 2_2 2_3
+ ]
+
+ document = REXML::Document.new(xml_source)
+
+ # Node#each_recursive iterates elements only.
+ # This does not iterate XML declerations, comments, attributes, CDATA sections, etc.
+ actual_names = []
+ document.each_recursive do |element|
+ actual_names << element.attributes["name"]
+ end
+ assert_equal(expected_names, actual_names)
+ end
+
class WriteTest < Test::Unit::TestCase
def setup
@document = REXML::Document.new(<<-EOX)