docstache 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'template_processor_spec'
3
+
4
+ describe 'integration test', integration: true do
5
+ let(:data) { Docstache::TestData::DATA }
6
+ let(:base_path) { SPEC_BASE_PATH.join('example_input') }
7
+ let(:input_file) { "#{base_path}/ExampleTemplate.docx" }
8
+ let(:output_dir) { "#{base_path}/tmp" }
9
+ let(:output_file) { "#{output_dir}/IntegrationTestOutput.docx" }
10
+ before do
11
+ FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
12
+ Dir.mkdir(output_dir)
13
+ end
14
+
15
+ context 'should process in incoming docx' do
16
+ it 'generates a valid zip file (.docx)' do
17
+ Docstache::Render.new(input_file, data).generate_docx_file(output_file)
18
+
19
+ archive = Zip::File.open(output_file)
20
+ archive.close
21
+
22
+ puts "\n************************************"
23
+ puts ' >>> Only will work on mac <<<'
24
+ puts 'NOW attempting to open created file in Word.'
25
+ cmd = "open #{output_file}"
26
+ puts " will run '#{cmd}'"
27
+ puts '************************************'
28
+
29
+ system cmd
30
+ end
31
+
32
+ it 'generates a file with the same contents as the input docx' do
33
+ input_entries = Zip::File.open(input_file) { |z| z.map(&:name) }
34
+ DocxTemplater::DocxCreator.new(input_file, data).generate_docx_file(output_file)
35
+ output_entries = Zip::File.open(output_file) { |z| z.map(&:name) }
36
+
37
+ expect(input_entries).to eq(output_entries)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,12 @@
1
+ require 'fileutils'
2
+ require 'docstache'
3
+
4
+ SPEC_BASE_PATH = Pathname.new(File.expand_path(File.dirname(__FILE__)))
5
+
6
+ RSpec.configure do |config|
7
+ [:expect_with, :mock_with].each do |method|
8
+ config.send(method, :rspec) do |c|
9
+ c.syntax = :expect
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,222 @@
1
+ require 'spec_helper'
2
+ require 'nokogiri'
3
+
4
+ module Docstache
5
+ module TestData
6
+ DATA = {
7
+ teacher: 'Priya Vora',
8
+ building: 'Building #14',
9
+ classroom: 'Rm 202'.to_sym,
10
+ district: 'Washington County Public Schools',
11
+ senority: 12.25,
12
+ roster: [
13
+ { name: 'Sally', age: 12, attendence: '100%' },
14
+ { name: :Xiao, age: 10, attendence: '94%' },
15
+ { name: 'Bryan', age: 13, attendence: '100%' },
16
+ { name: 'Larry', age: 11, attendence: '90%' },
17
+ { name: 'Kumar', age: 12, attendence: '76%' },
18
+ { name: 'Amber', age: 11, attendence: '100%' },
19
+ { name: 'Isaiah', age: 12, attendence: '89%' },
20
+ { name: 'Omar', age: 12, attendence: '99%' },
21
+ { name: 'Xi', age: 11, attendence: '20%' },
22
+ { name: 'Noushin', age: 12, attendence: '100%' }
23
+ ],
24
+ event_reports: [
25
+ { name: 'Science Museum Field Trip', notes: 'PTA sponsored event. Spoke to Astronaut with HAM radio.' },
26
+ { name: 'Wilderness Center Retreat', notes: '2 days hiking for charity:water fundraiser, $10,200 raised.' }
27
+ ],
28
+ created_at: '11-12-03 02:01'
29
+ }
30
+ end
31
+ end
32
+
33
+ describe Docstache::Render do
34
+ let(:data) { Marshal.load(Marshal.dump(DocxTemplater::TestData::DATA)) } # deep copy
35
+ let(:base_path) { SPEC_BASE_PATH.join('example_input') }
36
+ let(:xml) { File.read("#{base_path}/word/document.xml") }
37
+ let(:parser) { DocxTemplater::TemplateProcessor.new(data) }
38
+
39
+ context 'valid xml' do
40
+ it 'should render and still be valid XML' do
41
+ expect(Nokogiri::XML.parse(xml)).to be_xml
42
+ out = parser.render(xml)
43
+ expect(Nokogiri::XML.parse(out)).to be_xml
44
+ end
45
+
46
+ it 'should accept non-ascii characters' do
47
+ data[:teacher] = '老师'
48
+ out = parser.render(xml)
49
+ expect(out).to include('老师')
50
+ expect(Nokogiri::XML.parse(out)).to be_xml
51
+ end
52
+
53
+ it 'should escape as necessary invalid xml characters, if told to' do
54
+ data[:building] = '23rd & A #1 floor'
55
+ data[:classroom] = '--> 201 <!--'
56
+ data[:roster][0][:name] = '<#Ai & Bo>'
57
+ out = parser.render(xml)
58
+
59
+ expect(Nokogiri::XML.parse(out)).to be_xml
60
+ expect(out).to include('23rd &amp; A #1 floor')
61
+ expect(out).to include('--&gt; 201 &lt;!--')
62
+ expect(out).to include('&lt;#Ai &amp; Bo&gt;')
63
+ end
64
+
65
+ context 'not escape xml' do
66
+ let(:parser) { DocxTemplater::TemplateProcessor.new(data, false) }
67
+ it 'does not escape the xml attributes' do
68
+ data[:building] = '23rd <p>&amp;</p> #1 floor'
69
+ out = parser.render(xml)
70
+ expect(Nokogiri::XML.parse(out)).to be_xml
71
+ expect(out).to include('23rd <p>&amp;</p> #1 floor')
72
+ end
73
+ end
74
+ end
75
+
76
+ context 'unmatched begin and end row templates' do
77
+ it 'should not raise' do
78
+ xml = <<EOF
79
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
80
+ <w:body>
81
+ <w:tbl>
82
+ <w:tr><w:tc>
83
+ <w:p>
84
+ <w:r><w:t>#BEGIN_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
85
+ </w:p>
86
+ </w:tc></w:tr>
87
+ <w:tr><w:tc>
88
+ <w:p>
89
+ <w:r><w:t>#END_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
90
+ </w:p>
91
+ </w:tc></w:tr>
92
+ <w:tr><w:tc>
93
+ <w:p>
94
+ <w:r><w:t>#BEGIN_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
95
+ </w:p>
96
+ </w:tc></w:tr>
97
+ <w:tr><w:tc>
98
+ <w:p>
99
+ <w:r><w:t>#END_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
100
+ </w:p>
101
+ </w:tc></w:tr>
102
+ </w:tbl>
103
+ </w:body>
104
+ </xml>
105
+ EOF
106
+ expect { parser.render(xml) }.to_not raise_error
107
+ end
108
+
109
+ it 'should raise an exception' do
110
+ xml = <<EOF
111
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
112
+ <w:body>
113
+ <w:tbl>
114
+ <w:tr><w:tc>
115
+ <w:p>
116
+ <w:r><w:t>#BEGIN_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
117
+ </w:p>
118
+ </w:tc></w:tr>
119
+ <w:tr><w:tc>
120
+ <w:p>
121
+ <w:r><w:t>#END_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
122
+ </w:p>
123
+ </w:tc></w:tr>
124
+ <w:tr><w:tc>
125
+ <w:p>
126
+ <w:r><w:t>#BEGIN_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
127
+ </w:p>
128
+ </w:tc></w:tr>
129
+ </w:tbl>
130
+ </w:body>
131
+ </xml>
132
+ EOF
133
+ expect { parser.render(xml) }.to raise_error(/#END_ROW:EVENT_REPORTS# nil: true/)
134
+ end
135
+ end
136
+
137
+ it 'should enter no text for a nil value' do
138
+ xml = <<EOF
139
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
140
+ <w:body>
141
+ <w:p>Before.$KEY$After</w:p>
142
+ </w:body>
143
+ </xml>
144
+ EOF
145
+ actual = DocxTemplater::TemplateProcessor.new(key: nil).render(xml)
146
+ expected_xml = <<EOF
147
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
148
+ <w:body>
149
+ <w:p>Before.After</w:p>
150
+ </w:body>
151
+ </xml>
152
+ EOF
153
+ expect(actual).to eq(expected_xml)
154
+ end
155
+
156
+ it 'should replace all simple keys with values' do
157
+ non_array_keys = data.reject { |_, v| v.is_a?(Array) }
158
+ non_array_keys.keys.each do |key|
159
+ expect(xml).to include("$#{key.to_s.upcase}$")
160
+ expect(xml).not_to include(data[key].to_s)
161
+ end
162
+ out = parser.render(xml)
163
+
164
+ non_array_keys.each do |key|
165
+ expect(out).not_to include("$#{key}$")
166
+ expect(out).to include(data[key].to_s)
167
+ end
168
+ end
169
+
170
+ it 'should replace all array keys with values' do
171
+ expect(xml).to include('#BEGIN_ROW:')
172
+ expect(xml).to include('#END_ROW:')
173
+ expect(xml).to include('$EACH:')
174
+
175
+ out = parser.render(xml)
176
+
177
+ expect(out).not_to include('#BEGIN_ROW:')
178
+ expect(out).not_to include('#END_ROW:')
179
+ expect(out).not_to include('$EACH:')
180
+
181
+ [:roster, :event_reports].each do |key|
182
+ data[key].each do |row|
183
+ row.values.map(&:to_s).each do |row_value|
184
+ expect(out).to include(row_value)
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ it 'shold render students names in the same order as the data' do
191
+ out = parser.render(xml)
192
+ expect(out).to include('Sally')
193
+ expect(out).to include('Kumar')
194
+ expect(out.index('Kumar')).to be > out.index('Sally')
195
+ end
196
+
197
+ it 'shold render event reports names in the same order as the data' do
198
+ out = parser.render(xml)
199
+ expect(out).to include('Science Museum Field Trip')
200
+ expect(out).to include('Wilderness Center Retreat')
201
+ expect(out.index('Wilderness Center Retreat')).to be > out.index('Science Museum Field Trip')
202
+ end
203
+
204
+ it 'should render 2-line event reports in same order as docx' do
205
+ event_reports_starting_at = xml.index('#BEGIN_ROW:EVENT_REPORTS#')
206
+ expect(event_reports_starting_at).to be >= 0
207
+ expect(xml.index('$EACH:NAME$', event_reports_starting_at)).to be > event_reports_starting_at
208
+ expect(xml.index('$EACH:NOTES$', event_reports_starting_at)).to be > event_reports_starting_at
209
+ expect(xml.index('$EACH:NOTES$', event_reports_starting_at)).to be > xml.index('$EACH:NAME$', event_reports_starting_at)
210
+
211
+ out = parser.render(xml)
212
+ expect(out.index('PTA sponsored event. Spoke to Astronaut with HAM radio.')).to be > out.index('Science Museum Field Trip')
213
+ end
214
+
215
+ it 'should render sums of input data' do
216
+ expect(xml).to include('#SUM')
217
+ out = parser.render(xml)
218
+ expect(out).not_to include('#SUM')
219
+ expect(out).to include("#{data[:roster].count} Students")
220
+ expect(out).to include("#{data[:event_reports].count} Events")
221
+ end
222
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: docstache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Will Cosgrove
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubyzip
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ description: Integrates data into MS Word docx template files. Processing supports
42
+ loops and replacement of strings of data both outside and within loops.
43
+ email:
44
+ - will@willcosgrove.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.rdoc
53
+ - Rakefile
54
+ - docstache.gemspec
55
+ - lib/docstache.rb
56
+ - lib/docstache/document.rb
57
+ - lib/docstache/renderer.rb
58
+ - lib/docstache/version.rb
59
+ - spec/example_input/ExampleTemplate.docx
60
+ - spec/example_input/word/document.xml
61
+ - spec/integration_spec.rb
62
+ - spec/spec_helper.rb
63
+ - spec/template_processor_spec.rb
64
+ homepage: https://github.com/willcosgrove/docstache
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.2.2
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Merges Hash of Data into Word docx template files using mustache syntax
88
+ test_files:
89
+ - spec/example_input/ExampleTemplate.docx
90
+ - spec/example_input/word/document.xml
91
+ - spec/integration_spec.rb
92
+ - spec/spec_helper.rb
93
+ - spec/template_processor_spec.rb
94
+ has_rdoc: