Use static variables for Node#previous/#next

Instead of calculating the previous/next node on the fly this data is
now set automatically whenever a node is stored in a NodeSet with an
owner. While this introduces some overhead and complexity when adding or
removing nodes from a NodeSet, it greatly reduces the runtime overhead
of calling Node#previous or Node#next.
This commit is contained in:
Yorick Peterse 2016-09-04 21:07:35 +02:00
parent dd138981f6
commit 5a58b14137
No known key found for this signature in database
GPG Key ID: EDD30D2BEB691AC9
3 changed files with 179 additions and 26 deletions

View File

@ -10,6 +10,12 @@ module Oga
# @return [Oga::XML::NodeSet] # @return [Oga::XML::NodeSet]
attr_reader :node_set attr_reader :node_set
# @return [Oga::XML::Node]
attr_accessor :previous
# @return [Oga::XML::Node]
attr_accessor :next
# @param [Hash] options # @param [Hash] options
# #
# @option options [Oga::XML::NodeSet] :node_set The node set that this # @option options [Oga::XML::NodeSet] :node_set The node set that this
@ -27,6 +33,8 @@ module Oga
@node_set = set @node_set = set
@root_node = nil @root_node = nil
@html_p = nil @html_p = nil
@previous = nil
@next = nil
end end
# Returns the child nodes of the current node. # Returns the child nodes of the current node.
@ -55,25 +63,6 @@ module Oga
node_set ? node_set.owner : nil node_set ? node_set.owner : nil
end end
# Returns the preceding node, or nil if there is none.
#
# @return [Oga::XML::Node]
def previous
index = node_set.index(self) - 1
index >= 0 ? node_set[index] : nil
end
# Returns the following node, or nil if there is none.
#
# @return [Oga::XML::Node]
def next
index = node_set.index(self) + 1
length = node_set.length
index < length ? node_set[index] : nil
end
# Returns the previous element node or nil if there is none. # Returns the previous element node or nil if there is none.
# #
# @return [Oga::XML::Element] # @return [Oga::XML::Element]

View File

@ -42,10 +42,10 @@ module Oga
@owner = owner @owner = owner
@existing = {} @existing = {}
@nodes.each do |node| @nodes.each_with_index do |node, index|
mark_existing(node) mark_existing(node)
take_ownership(node) if @owner take_ownership(node, index) if @owner
end end
end end
@ -98,7 +98,7 @@ module Oga
mark_existing(node) mark_existing(node)
take_ownership(node) if @owner take_ownership(node, length - 1) if @owner
end end
alias_method :<<, :push alias_method :<<, :push
@ -113,7 +113,7 @@ module Oga
mark_existing(node) mark_existing(node)
take_ownership(node) if @owner take_ownership(node, 0) if @owner
end end
# Shifts a node from the start of the set. # Shifts a node from the start of the set.
@ -157,7 +157,7 @@ module Oga
mark_existing(node) mark_existing(node)
take_ownership(node) if @owner take_ownership(node, index) if @owner
end end
# Returns the node for the given index. # Returns the node for the given index.
@ -224,6 +224,8 @@ module Oga
sets << node.node_set sets << node.node_set
node.node_set = nil node.node_set = nil
node.next = nil
node.previous = nil
end end
end end
@ -291,15 +293,34 @@ module Oga
# set has an owner. # set has an owner.
# #
# @param [Oga::XML::Node] node # @param [Oga::XML::Node] node
def take_ownership(node) # @param [Fixnum] index
def take_ownership(node, index)
node.node_set = self node.node_set = self
node.previous = index > 0 ? @nodes[index - 1] : nil
node.next = index + 1 < @nodes.length ? @nodes[index + 1] : nil
node.previous.next = node if node.previous
node.next.previous = node if node.next
end end
# Removes ownership of the node if it belongs to the current set. # Removes ownership of the node if it belongs to the current set.
# #
# @param [Oga::XML::Node] node # @param [Oga::XML::Node] node
def remove_ownership(node) def remove_ownership(node)
node.node_set = nil if node.node_set == self return unless node.node_set == self
if previous_node = node.previous
previous_node.next = node.next
end
if next_node = node.next
next_node.previous = node.previous
end
node.node_set = nil
node.previous = nil
node.next = nil
end end
# @param [Oga::XML::Node|Oga::XML::Attribute] node # @param [Oga::XML::Node|Oga::XML::Attribute] node

View File

@ -25,6 +25,30 @@ describe Oga::XML::NodeSet do
node.node_set.should == set node.node_set.should == set
end end
it 'sets the previous and next nodes for all nodes owned by the set' do
node1 = Oga::XML::Element.new
node2 = Oga::XML::Element.new
set = described_class.new([node1, node2], node1)
node1.next.should == node2
node1.previous.should == nil
node2.next.should == nil
node2.previous.should == node1
end
it 'does not set the previous and next nodes for nodes that are not owned' do
node1 = Oga::XML::Element.new
node2 = Oga::XML::Element.new
set = described_class.new([node1, node2])
node1.previous.should == nil
node1.next.should == nil
node2.previous.should == nil
node2.next.should == nil
end
end end
describe '#each' do describe '#each' do
@ -111,6 +135,22 @@ describe Oga::XML::NodeSet do
child.node_set.should == @set child.node_set.should == @set
end end
it 'updates the previous and next nodes of any owned nodes' do
node1 = Oga::XML::Element.new
node2 = Oga::XML::Element.new
@set.owner = node1
@set.push(node1)
@set.push(node2)
node1.next.should == node2
node1.previous.should == nil
node2.next.should == nil
node2.previous.should == node1
end
end end
describe '#unshift' do describe '#unshift' do
@ -141,6 +181,24 @@ describe Oga::XML::NodeSet do
child.node_set.should == @set child.node_set.should == @set
end end
it 'updates the next node of the added node' do
node = Oga::XML::Element.new
@set.owner = node
@set.unshift(node)
node.next.should == @n1
end
it 'updates the previous node of the existing node' do
node = Oga::XML::Element.new
@set.owner = node
@set.unshift(node)
@n1.previous.should == node
end
end end
describe '#shift' do describe '#shift' do
@ -164,6 +222,22 @@ describe Oga::XML::NodeSet do
@n1.node_set.nil?.should == true @n1.node_set.nil?.should == true
end end
it 'updates the previous and next nodes of the removed node' do
@set.shift
@n1.previous.should == nil
@n1.next.should == nil
end
it 'updates the previous node of the remaining node' do
node = Oga::XML::Element.new
@set.push(node)
@set.shift
node.previous.should == nil
end
end end
describe '#pop' do describe '#pop' do
@ -187,6 +261,21 @@ describe Oga::XML::NodeSet do
@n1.node_set.nil?.should == true @n1.node_set.nil?.should == true
end end
it 'updates the previous node of the removed node' do
@set.pop
@n1.previous.should == nil
end
it 'updates the next node of the last remaining node' do
node = Oga::XML::Element.new
@set.push(node)
@set.pop
@n1.next.should == nil
end
end end
describe '#insert' do describe '#insert' do
@ -230,6 +319,40 @@ describe Oga::XML::NodeSet do
node.node_set.should == @owned_set node.node_set.should == @owned_set
end end
it 'updates the previous and next nodes of the inserted node' do
node1 = Oga::XML::Element.new
node2 = Oga::XML::Element.new
node3 = Oga::XML::Element.new
@owned_set.push(node1)
@owned_set.push(node2)
@owned_set.insert(1, node3)
node3.previous.should == node1
node3.next.should == node2
end
it 'updates the next node of the node preceding the inserted node' do
node1 = Oga::XML::Element.new
node2 = Oga::XML::Element.new
@owned_set.push(node1)
@owned_set.insert(1, node2)
node1.next.should == node2
end
it 'updates the previous node of the node following the inserted node' do
node1 = Oga::XML::Element.new
node2 = Oga::XML::Element.new
@owned_set.push(node1)
@owned_set.insert(0, node2)
node1.previous.should == node2
end
end end
describe '#[]' do describe '#[]' do
@ -338,6 +461,16 @@ describe Oga::XML::NodeSet do
@doc_set.empty?.should == true @doc_set.empty?.should == true
end end
it 'updates the previous and next nodes for all removed nodes' do
@doc_set.remove
@n1.previous.should == nil
@n1.next.should == nil
@n2.previous.should == nil
@n2.next.should == nil
end
end end
describe '#delete' do describe '#delete' do
@ -362,6 +495,16 @@ describe Oga::XML::NodeSet do
@n1.node_set.nil?.should == true @n1.node_set.nil?.should == true
end end
it 'updates the previous and next nodes of the removed node' do
node = Oga::XML::Element.new
@set.push(node)
@set.delete(@n1)
@n1.previous.should == nil
@n1.next.should == nil
end
end end
describe '#attribute' do describe '#attribute' do