diff --git a/lib/docx/elements/style.rb b/lib/docx/elements/style.rb index 848e63b..4ae4611 100644 --- a/lib/docx/elements/style.rb +++ b/lib/docx/elements/style.rb @@ -8,64 +8,89 @@ module Elements class Style include Docx::SimpleInspect - @attributes = [] + class Attribute + attr_reader :name, :selectors, :required, :converter, :validator + + def initialize(name, selectors, required: false, converter:, validator:) + @name = name + @selectors = selectors + @required = required + @converter = converter || Converters::DefaultValueConverter + @validator = validator || Validators::DefaultValidator + end - class << self - attr_accessor :attributes + def required? + required + end - def required_attributes - attributes.select { |a| a[:required] } + def retrieve_from(style) + selectors + .lazy + .filter_map { |node_xpath| style.node.at_xpath(node_xpath)&.value } + .map { |value| converter.decode(value) } + .first end - def attribute(name, *selectors, required: false, converter: Converters::DefaultValueConverter, validator: Validators::DefaultValidator) - attributes << {name: name, selectors: selectors, required: required, converter: converter, validator: validator} + def assign_to(style, value) + (required && value.nil?) && + raise(Errors::StyleRequiredPropertyValue, "Required value #{name}") - define_method(name) do - selectors - .lazy - .filter_map { |node_xpath| node.at_xpath(node_xpath)&.value } - .map { |value| converter.decode(value) } - .first - end + validator.validate(value) || + raise(Errors::StyleInvalidPropertyValue, "Invalid value for #{name}: '#{value.nil? ? "nil" : value}'") - define_method("#{name}=") do |value| - (required && value.nil?) && - raise(Errors::StyleRequiredPropertyValue, "Required value #{name}") + encoded_value = converter.encode(value) - validator.validate(value) || - raise(Errors::StyleInvalidPropertyValue, "Invalid value for #{name}: '#{value.nil? ? "nil" : value}'") + selectors.map do |attribute_xpath| + if (existing_attribute = style.node.at_xpath(attribute_xpath)) + if encoded_value.nil? + existing_attribute.remove + else + existing_attribute.value = encoded_value.to_s + end - encoded_value = converter.encode(value) + next encoded_value + end - selectors.map do |attribute_xpath| - if (existing_attribute = node.at_xpath(attribute_xpath)) - if encoded_value.nil? - existing_attribute.remove - else - existing_attribute.value = encoded_value.to_s + next encoded_value if encoded_value.nil? + + node_xpath, attribute = attribute_xpath.split("/@") + + created_node = + node_xpath + .split("/") + .reduce(style.node) do |parent_node, child_xpath| + # find the child node + parent_node.at_xpath(child_xpath) || + # or create the child node + Nokogiri::XML::Node.new(child_xpath, parent_node) + .tap { |created_child_node| parent_node << created_child_node } end - next encoded_value - end + created_node.set_attribute(attribute, encoded_value) + end + .first + end + end + + @attributes = [] + + class << self + attr_accessor :attributes - next encoded_value if encoded_value.nil? + def required_attributes + attributes.select(&:required?) + end - node_xpath, attribute = attribute_xpath.split("/@") + def attribute(name, *selectors, required: false, converter: nil, validator: nil) + new_attribute = Attribute.new(name, selectors, required: required, converter: converter, validator: validator) + attributes << new_attribute - created_node = - node_xpath - .split("/") - .reduce(node) do |parent_node, child_xpath| - # find the child node - parent_node.at_xpath(child_xpath) || - # or create the child node - Nokogiri::XML::Node.new(child_xpath, parent_node) - .tap { |created_child_node| parent_node << created_child_node } - end + define_method(name) do + new_attribute.retrieve_from(self) + end - created_node.set_attribute(attribute, encoded_value) - end - .first + define_method("#{name}=") do |value| + new_attribute.assign_to(self, value) end end @@ -135,11 +160,9 @@ def initialize(configuration, node, **attributes) def valid? self.class.required_attributes.all? do |a| - validator = a[:validator] - attribute_name = a[:name] - attribute_value = self.send(attribute_name) + attribute_value = a.retrieve_from(self) - validator&.validate(attribute_value) + a.validator&.validate(attribute_value) end end