Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 79 additions & 4 deletions lib/rubyXL/objects/ooxml_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,59 @@ def obtain_class_variable(var_name, default = {})
self.class_variable_set(var_name, default)
end

# This is like attr_accessor method, creating the getter and setter
# However, in this case the setter update the hash of the object containing it.
# It also trigger the parent in case of change
def hash_attr_accessor symbol
class_eval <<-RUBY, __FILE__, __LINE__
def #{symbol}
@#{symbol}
end

def #{symbol}= value
str_hash = "#{symbol}".hash

before_hash = @hash
@hash ^= @#{symbol}.hash * str_hash # Outmerge the hash component
@#{symbol} = value
@hash ^= value.hash * str_hash # Merge again the new hash component

@parent.hash_dirty! if @parent

value
end
RUBY
end

# This is like attr_accessor method, creating the getter and setter
# However, in this case the setter update the hash of the object containing it.
# It also register the parent link on setup
def hash_attr_accessor_child_node symbol
class_eval <<-RUBY, __FILE__, __LINE__
def #{symbol}
@#{symbol}
end

def #{symbol}= new_value
str_hash = "#{symbol}".hash

old_value = @#{symbol}

old_value.parent = nil if old_value.is_a?(OOXMLObjectInstanceMethods)
new_value.parent = self if new_value.is_a?(OOXMLObjectInstanceMethods)

before_hash = @hash
@hash ^= old_value.hash * str_hash # Outmerge the hash component
@#{symbol} = new_value
@hash ^= new_value.hash * str_hash # Merge again the new hash component

@parent.hash_dirty! if @parent

new_value
end
RUBY
end

# Defines an attribute of OOXML object.
# === Parameters
# * +attribute_name+ - Name of the element attribute as seen in the source XML. Can be either <tt>"String"</tt> or <tt>:Symbol</tt>
Expand Down Expand Up @@ -50,7 +103,7 @@ def define_attribute(attr_name, attr_type, extra_params = {})
attr_hash = extra_params.merge({ :attr_type => attr_type })
attr_hash[:accessor] ||= accessorize(attr_name)
attrs[attr_name.to_s] = attr_hash
self.send(:attr_accessor, attr_hash[:accessor]) unless attr_hash[:computed]
self.send(:hash_attr_accessor, attr_hash[:accessor]) unless attr_hash[:computed]
end

# Defines a child node of OOXML object.
Expand Down Expand Up @@ -86,7 +139,7 @@ def define_child_node(klass, extra_params = {})

define_count_attribute if extra_params[:collection] == :with_count

self.send(:attr_accessor, accessor)
self.send(:hash_attr_accessor_child_node, accessor)
end

def define_count_attribute
Expand Down Expand Up @@ -208,6 +261,10 @@ def process_attribute(obj, raw_value, params)
module OOXMLObjectInstanceMethods
attr_accessor :local_namespaces

# Accessor used to get back to the parent from this node,
# and dirtyize the parent in case of hash update.
attr_accessor :parent

def self.included(klass)
klass.extend RubyXL::OOXMLObjectClassMethods
end
Expand All @@ -225,6 +282,7 @@ def initialize(params = {})
}

init_child_nodes(params)
recompute_hash
end

def init_child_nodes(params)
Expand All @@ -246,10 +304,27 @@ def preserve_whitespace
end
private :preserve_whitespace

# Manually update a hashing part.
def hash_dirty!
@need_hash_recompute = true
end

# Recompute the hash with segment given by the childrens
def recompute_hash
@hash = 0
@hash = obtain_class_variable(:@@ooxml_attributes).inject(@hash) { |h, (k, v)| h ^ ( v[:accessor].hash * self.send(v[:accessor]).hash ) }
@hash = obtain_class_variable(:@@ooxml_child_nodes).inject(@hash) { |h, (k, v)| h ^ ( v[:accessor].hash * self.send(v[:accessor]).hash ) }
@need_hash_recompute = false
end

def hash
recompute_hash if @need_hash_recompute
@hash
end

def ==(other)
other.is_a?(self.class) &&
obtain_class_variable(:@@ooxml_attributes).all? { |k, v| self.send(v[:accessor]) == other.send(v[:accessor]) } &&
obtain_class_variable(:@@ooxml_child_nodes).all? { |k, v| self.send(v[:accessor]) == other.send(v[:accessor]) }
self.hash == other.hash
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because the hashes are equal does not mean the attributes are equal.

You will still need to do the comparison of attributes when the hashes are equal, the alternative may be to use SHA512 hashes or something.

end

# Recursively write the OOXML object and all its children out as Nokogiri::XML. Immediately before the actual
Expand Down