diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2979906..a5baa9e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,11 +1,19 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2017-05-03 18:43:13 +0300 using RuboCop version 0.48.1. +# on 2017-05-29 13:33:52 +0300 using RuboCop version 0.49.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent +Layout/IndentHeredoc: + Exclude: + - 'spec/template_processor_spec.rb' + # Offense count: 2 Metrics/AbcSize: Max: 54 @@ -13,18 +21,18 @@ Metrics/AbcSize: # Offense count: 5 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: - Max: 177 + Max: 191 -# Offense count: 23 +# Offense count: 26 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: - Max: 255 + Max: 211 # Offense count: 2 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 35 + Max: 38 # Offense count: 3 Style/Documentation: @@ -34,11 +42,3 @@ Style/Documentation: - 'lib/docx_templater.rb' - 'lib/docx_templater/docx_creator.rb' - 'lib/docx_templater/template_processor.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent -Style/IndentHeredoc: - Exclude: - - 'spec/template_processor_spec.rb' diff --git a/lib/docx_templater/docx_creator.rb b/lib/docx_templater/docx_creator.rb index c4e4c9a..606cbd0 100644 --- a/lib/docx_templater/docx_creator.rb +++ b/lib/docx_templater/docx_creator.rb @@ -4,9 +4,9 @@ module DocxTemplater class DocxCreator attr_reader :template_path, :template_processor - def initialize(template_path, data, escape_html = true) + def initialize(template_path, data, escape_html = true, skip_unmatched: false) @template_path = template_path - @template_processor = TemplateProcessor.new(data, escape_html) + @template_processor = TemplateProcessor.new(data, escape_html, skip_unmatched: skip_unmatched) end def generate_docx_file(file_name = "output_#{Time.now.strftime('%Y-%m-%d_%H%M')}.docx") @@ -20,7 +20,9 @@ def generate_docx_bytes Zip::File.open(template_path).each do |entry| entry_name = entry.name out.put_next_entry(entry_name) - out.write(copy_or_template(entry_name, entry.get_input_stream.read)) + unless entry.directory? + out.write(copy_or_template(entry_name, entry.get_input_stream.read)) + end end end end diff --git a/lib/docx_templater/template_processor.rb b/lib/docx_templater/template_processor.rb index ae12348..c27e56d 100644 --- a/lib/docx_templater/template_processor.rb +++ b/lib/docx_templater/template_processor.rb @@ -2,12 +2,13 @@ module DocxTemplater class TemplateProcessor - attr_reader :data, :escape_html + attr_reader :data, :escape_html, :skip_unmatched # data is expected to be a hash of symbols => string or arrays of hashes. - def initialize(data, escape_html = true) + def initialize(data, escape_html = true, skip_unmatched: false) @data = data @escape_html = escape_html + @skip_unmatched = skip_unmatched end def render(document) @@ -15,7 +16,7 @@ def render(document) data.each do |key, value| case value when Array - document = enter_multiple_values(document, key) + document = enter_multiple_values(document, key, data[key]) document.gsub!("#SUM:#{key.to_s.upcase}#", value.count.to_s) when TrueClass, FalseClass if value @@ -40,10 +41,8 @@ def safe(text) end end - def enter_multiple_values(document, key) - DocxTemplater.log("enter_multiple_values for: #{key}") - # TODO: ideally we would not re-parse xml doc every time - xml = Nokogiri::XML(document) + def enter_multiple_values xml, key, values + xml = Nokogiri::XML(xml) begin_row = "#BEGIN_ROW:#{key.to_s.upcase}#" end_row = "#END_ROW:#{key.to_s.upcase}#" @@ -51,7 +50,10 @@ def enter_multiple_values(document, key) end_row_template = xml.xpath("//w:tr[contains(., '#{end_row}')]", xml.root.namespaces).first DocxTemplater.log("begin_row_template: #{begin_row_template}") DocxTemplater.log("end_row_template: #{end_row_template}") - raise "unmatched template markers: #{begin_row} nil: #{begin_row_template.nil?}, #{end_row} nil: #{end_row_template.nil?}. This could be because word broke up tags with it's own xml entries. See README." unless begin_row_template && end_row_template + unless begin_row_template && end_row_template + return as_result(xml) if @skip_unmatched + raise "unmatched template markers: #{begin_row} nil: #{begin_row_template.nil?}, #{end_row} nil: #{end_row_template.nil?}. This could be because word broke up tags with it's own xml entries. See README." + end row_templates = [] row = begin_row_template.next_sibling @@ -62,11 +64,26 @@ def enter_multiple_values(document, key) DocxTemplater.log("row_templates: (#{row_templates.count}) #{row_templates.map(&:to_s).inspect}") # for each data, reversed so they come out in the right order - data[key].reverse_each do |each_data| - DocxTemplater.log("each_data: #{each_data.inspect}") + values.reverse_each do |data| + DocxTemplater.log("each_data: #{data.inspect}") + rt = row_templates.map(&:dup) + + each_data = {} + data.each do |k, v| + if v.is_a?(Array) + doc = Nokogiri::XML::Document.new + root = doc.create_element 'pseudo_root', xml.root.namespaces + root.inner_html = rt.reverse.map{|x| x.to_xml}.join + q = enter_multiple_values root.to_xml, k, v + rt = xml.parse(q).reverse + else + each_data[k] = v + end + end + # dup so we have new nodes to append - row_templates.map(&:dup).each do |new_row| + rt.map(&:dup).each do |new_row| DocxTemplater.log(" new_row: #{new_row}") innards = new_row.inner_html matches = innards.scan(/\$EACH:([^\$]+)\$/) @@ -80,12 +97,20 @@ def enter_multiple_values(document, key) # change all the internals of the new node, even if we did not template new_row.inner_html = innards # DocxTemplater::log("new_row new innards: #{new_row.inner_html}") - begin_row_template.add_next_sibling(new_row) end end (row_templates + [begin_row_template, end_row_template]).each(&:unlink) - xml.to_s + as_result xml + end + + def as_result xml + if xml.root.name == 'pseudo_root' + xml.root.inner_html + else + xml.to_s + end end + end end diff --git a/spec/template_processor_spec.rb b/spec/template_processor_spec.rb index ed20170..b708d3b 100644 --- a/spec/template_processor_spec.rb +++ b/spec/template_processor_spec.rb @@ -75,6 +75,22 @@ module TestData expect(out).to include('23rd
&
#1 floor') end end + context 'when unmatched array' do + let(:unmatched_data) do + data.merge(unmatched_array: ['Some data']) + end + + it 'raised' do + parser = DocxTemplater::TemplateProcessor.new(unmatched_data) + expect { parser.render(xml) }.to raise_error + end + + it 'skipped' do + parser = DocxTemplater::TemplateProcessor.new(unmatched_data, skip_unmatched: true) + out = parser.render(xml) + expect(Nokogiri::XML.parse(out)).to be_xml + end + end end context 'unmatched begin and end row templates' do