From a4ba108eb33cba35f87370b7e4b47bff18130e89 Mon Sep 17 00:00:00 2001 From: Paul Kmiec Date: Sat, 29 Apr 2023 16:24:22 -0700 Subject: Serialize attributes more efficiently The attributes in rows, cells, styles, etc are pre-defined for each type and almost never change. The current code, however, does not take that into account by reading all instance variables (via declared_attributes), filtering out the blank one and ones that are not xml attributes, and by camelizing each one. We can avoid all this extra work by computing the camels and ivars for xml attributes once and directly read the instance variables we care about. --- lib/axlsx.rb | 1 + lib/axlsx/stylesheet/num_fmt.rb | 6 +--- lib/axlsx/util/serialized_attributes.rb | 53 +++++++++++++++++++++++---------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/lib/axlsx.rb b/lib/axlsx.rb index 4f15ba23..776ff918 100644 --- a/lib/axlsx.rb +++ b/lib/axlsx.rb @@ -171,6 +171,7 @@ module Axlsx # @return [Object] def self.booleanize(value) if value == true || value == false + # value ? '1' : '0' value ? 1 : 0 else value diff --git a/lib/axlsx/stylesheet/num_fmt.rb b/lib/axlsx/stylesheet/num_fmt.rb index 1a7add0e..95d9a62b 100644 --- a/lib/axlsx/stylesheet/num_fmt.rb +++ b/lib/axlsx/stylesheet/num_fmt.rb @@ -75,11 +75,7 @@ module Axlsx # Override to avoid removing underscores def serialized_attributes(str = +'', additional_attributes = {}) - attributes = declared_attributes.merge! additional_attributes - attributes.each do |key, value| - str << "#{Axlsx.camel(key, false)}=\"#{Axlsx.booleanize(value)}\" " - end - str + super(str, additional_attributes, false) end end end diff --git a/lib/axlsx/util/serialized_attributes.rb b/lib/axlsx/util/serialized_attributes.rb index b7247e0d..b2b63fe1 100644 --- a/lib/axlsx/util/serialized_attributes.rb +++ b/lib/axlsx/util/serialized_attributes.rb @@ -15,11 +15,21 @@ module Axlsx # which of the instance values are serializable def serializable_attributes(*symbols) @xml_attributes = symbols + @camel_xml_attributes = nil + @ivar_xml_attributes = nil end # a reader for those attributes attr_reader :xml_attributes + def camel_xml_attributes + @camel_xml_attributes ||= @xml_attributes.map { |attr| Axlsx.camel(attr, false) } + end + + def ivar_xml_attributes + @ivar_xml_attributes ||= @xml_attributes.map { |attr| :"@#{attr}" } + end + # This helper registers the attributes that will be formatted as elements. def serializable_element_attributes(*symbols) @xml_element_attributes = symbols @@ -32,12 +42,12 @@ module Axlsx # creates a XML tag with serialized attributes # @see SerializedAttributes#serialized_attributes def serialized_tag(tagname, str, additional_attributes = {}, &block) - str << "<#{tagname} " + str << '<' << tagname << ' ' serialized_attributes(str, additional_attributes) if block_given? str << '>' yield - str << "" + str << '' else str << '/>' end @@ -49,21 +59,34 @@ module Axlsx # serialization to. # @param [Hash] additional_attributes An option key value hash for # defining values that are not serializable attributes list. - def serialized_attributes(str = +'', additional_attributes = {}) - attributes = declared_attributes.merge! additional_attributes - attributes.each do |key, value| - str << "#{Axlsx.camel(key, false)}=\"#{Axlsx.camel(Axlsx.booleanize(value), false)}\" " + # @param [Boolean] camelize_value Should the attribute values be camelized + def serialized_attributes(str = +'', additional_attributes = {}, camelize_value = true) + camel_xml_attributes = self.class.camel_xml_attributes + ivar_xml_attributes = self.class.ivar_xml_attributes + + self.class.xml_attributes.each_with_index do |attr, index| + next if additional_attributes.key?(attr) + next unless instance_variable_defined?(ivar_xml_attributes[index]) + + value = instance_variable_get(ivar_xml_attributes[index]) + next if value.nil? + + value = Axlsx.booleanize(value) + value = Axlsx.camel(value, false) if camelize_value + + str << camel_xml_attributes[index] << '="' << value.to_s << '" ' end - str - end - # A hash of instance variables that have been declared with - # seraialized_attributes and are not nil. - # This requires ruby 1.9.3 or higher - def declared_attributes - Axlsx.instance_values_for(self).select do |key, value| - value != nil && self.class.xml_attributes.include?(key.to_sym) + additional_attributes.each do |attr, value| + next if value.nil? + + value = Axlsx.booleanize(value) + value = Axlsx.camel(value, false) if camelize_value + + str << Axlsx.camel(attr, false) << '="' << value.to_s << '" ' end + + str end # serialized instance values at text nodes on a camelized element of the @@ -82,7 +105,7 @@ module Axlsx value = yield value if block_given? element_name = Axlsx.camel(attribute_name, false) - str << "<#{element_name}>#{value}" + str << '<' << element_name << '>' << value << '' end str end -- cgit v1.2.3 From 2b42f259552dd3f443a1393eb8b47e8dc9ee80f2 Mon Sep 17 00:00:00 2001 From: Paul Kmiec Date: Mon, 15 May 2023 23:26:13 -0700 Subject: Booleanize as string instead of 0 / 1 only to call to_s The `booleanize` method is always used to pipe values to str, so it is safe to return the boolean values as strings. --- lib/axlsx.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/axlsx.rb b/lib/axlsx.rb index 776ff918..656db8aa 100644 --- a/lib/axlsx.rb +++ b/lib/axlsx.rb @@ -171,8 +171,7 @@ module Axlsx # @return [Object] def self.booleanize(value) if value == true || value == false - # value ? '1' : '0' - value ? 1 : 0 + value ? '1' : '0' else value end -- cgit v1.2.3 From 7d70e17cd818958eac09f68c8886b9664aa4ad8c Mon Sep 17 00:00:00 2001 From: Paul Kmiec Date: Mon, 15 May 2023 23:33:15 -0700 Subject: Fix rubocop offenses We still need Style/OptionalBooleanParameter as Ruby 2.7.5 gets confused with, ``` def serialized_attributes(str = +'', additional_attributes = {}, camelize_value: true) ``` --- .rubocop_todo.yml | 8 +------- lib/axlsx/util/serialized_attributes.rb | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2385ecb2..dde13026 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -79,7 +79,6 @@ Lint/UnusedBlockArgument: Lint/UnusedMethodArgument: Exclude: - 'lib/axlsx/package.rb' - - 'lib/axlsx/util/serialized_attributes.rb' - 'lib/axlsx/util/validators.rb' Lint/UselessAssignment: @@ -95,11 +94,6 @@ Lint/Void: - 'lib/axlsx/workbook/worksheet/data_bar.rb' - 'lib/axlsx/workbook/worksheet/pivot_table.rb' -# This cop supports safe autocorrection (--autocorrect). -Performance/BlockGivenWithExplicitBlock: - Exclude: - - 'lib/axlsx/util/serialized_attributes.rb' - # Configuration parameters: MinSize. Performance/CollectionLiteralInLoop: Exclude: @@ -369,7 +363,6 @@ Style/Next: Style/NonNilCheck: Exclude: - 'lib/axlsx/drawing/d_lbls.rb' - - 'lib/axlsx/util/serialized_attributes.rb' - 'lib/axlsx/workbook/worksheet/col.rb' - 'lib/axlsx/workbook/worksheet/page_setup.rb' @@ -398,6 +391,7 @@ Style/OptionalBooleanParameter: Exclude: - 'lib/axlsx.rb' - 'lib/axlsx/package.rb' + - 'lib/axlsx/util/serialized_attributes.rb' - 'lib/axlsx/util/validators.rb' - 'lib/axlsx/workbook/workbook.rb' - 'lib/axlsx/workbook/worksheet/cell.rb' diff --git a/lib/axlsx/util/serialized_attributes.rb b/lib/axlsx/util/serialized_attributes.rb index b2b63fe1..27e19904 100644 --- a/lib/axlsx/util/serialized_attributes.rb +++ b/lib/axlsx/util/serialized_attributes.rb @@ -41,7 +41,7 @@ module Axlsx # creates a XML tag with serialized attributes # @see SerializedAttributes#serialized_attributes - def serialized_tag(tagname, str, additional_attributes = {}, &block) + def serialized_tag(tagname, str, additional_attributes = {}) str << '<' << tagname << ' ' serialized_attributes(str, additional_attributes) if block_given? @@ -96,7 +96,7 @@ module Axlsx # @param [String] str The string instance to which serialized data is appended # @param [Array] additional_attributes An array of additional attribute names. # @return [String] The serialized output. - def serialized_element_attributes(str = +'', additional_attributes = [], &block) + def serialized_element_attributes(str = +'', additional_attributes = []) attrs = self.class.xml_element_attributes + additional_attributes values = Axlsx.instance_values_for(self) attrs.each do |attribute_name| -- cgit v1.2.3