template_docx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'template_processor_spec'
3
+
4
+ describe 'integration test', integration: true do
5
+ let(:data) { DocxTemplater::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
+
11
+ let(:brand_data) { DocxTemplater::BrandData::DATA }
12
+ let(:brand_input_file) { "#{base_path}/ExampleBrand.docx" }
13
+ let(:brand_output_file) { "#{output_dir}/IntegrationBrandOutput.docx" }
14
+
15
+ before do
16
+ FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
17
+ Dir.mkdir(output_dir)
18
+ end
19
+
20
+ context 'should process in incoming docx' do
21
+
22
+ # TODO: 这两个生成 DOC 文件的 测试用例,不能同时执行
23
+ it 'generates a valid zip file (.docx)' do
24
+ DocxTemplater::DocxCreator.new(input_file, data).generate_docx_file(output_file)
25
+
26
+ archive = Zip::File.open(output_file)
27
+ archive.close
28
+
29
+ DocxTemplater.open_output_file output_file
30
+ end
31
+
32
+ it 'generates a valid doc file with brand data' do
33
+ DocxTemplater::DocxCreator.new(brand_input_file, brand_data).generate_docx_file(brand_output_file)
34
+
35
+ archive = Zip::File.open(brand_output_file)
36
+ archive.close
37
+
38
+ DocxTemplater.open_output_file brand_output_file
39
+ end
40
+
41
+ it 'generates a file with the same contents as the input docx' do
42
+ input_entries = Zip::File.open(input_file) { |z| z.map(&:name) }
43
+ DocxTemplater::DocxCreator.new(input_file, data).generate_docx_file(output_file)
44
+ output_entries = Zip::File.open(output_file) { |z| z.map(&:name) }
45
+
46
+ expect(input_entries).to eq(output_entries)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,12 @@
1
+ require 'fileutils'
2
+ require 'docx_templater'
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,240 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+ require 'nokogiri'
5
+
6
+ module DocxTemplater
7
+ module TestData
8
+ DATA = {
9
+ teacher: 'Priya Vora',
10
+ building: 'Building #14',
11
+ classroom: 'Rm 202'.to_sym,
12
+ district: 'Washington County Public Schools',
13
+ senority: 12.25,
14
+ roster: [
15
+ { name: 'Sally', age: 12, attendence: '100%' },
16
+ { name: :Xiao, age: 10, attendence: '94%' },
17
+ { name: 'Bryan', age: 13, attendence: '100%' },
18
+ { name: 'Larry', age: 11, attendence: '90%' },
19
+ { name: 'Kumar', age: 12, attendence: '76%' },
20
+ { name: 'Amber', age: 11, attendence: '100%' },
21
+ { name: 'Isaiah', age: 12, attendence: '89%' },
22
+ { name: 'Omar', age: 12, attendence: '99%' },
23
+ { name: 'Xi', age: 11, attendence: '20%' },
24
+ { name: 'Noushin', age: 12, attendence: '100%' }
25
+ ],
26
+ event_reports: [
27
+ { name: 'Science Museum Field Trip', notes: 'PTA sponsored event. Spoke to Astronaut with HAM radio.' },
28
+ { name: 'Wilderness Center Retreat', notes: '2 days hiking for charity:water fundraiser, $10,200 raised.' }
29
+ ],
30
+ created_at: '11-12-03 02:01'
31
+ }
32
+ end
33
+
34
+ module BrandData
35
+ DATA = {
36
+ company: '伟大的邪王真眼',
37
+ shop_name: '斜阳西下,三百二十七',
38
+ shop_id: 122323232323,
39
+ shop_url: 'http://blog.csdn.net/ruixj/article/details/3765385',
40
+ master_company: '紫电清爽公司',
41
+ brand_name: '邪王正眼',
42
+ start_date: (Time.now - 3600 * 24).strftime('%Y年-%m月-%d日'),
43
+ end_date: Time.now.strftime('%Y年-%m月-%d日'),
44
+ license_number: 1212121212,
45
+ authorized_party: '夏健的夏天',
46
+ authorized_date: Time.now.strftime('%Y年-%m月-%d日')
47
+ }
48
+ end
49
+ end
50
+
51
+ describe DocxTemplater::TemplateProcessor do
52
+ let(:data) { Marshal.load(Marshal.dump(DocxTemplater::TestData::DATA)) } # deep copy
53
+ let(:base_path) { SPEC_BASE_PATH.join('example_input') }
54
+ let(:xml) { File.read("#{base_path}/word/document.xml") }
55
+ let(:parser) { DocxTemplater::TemplateProcessor.new(data) }
56
+
57
+ context 'valid xml' do
58
+ it 'should render and still be valid XML' do
59
+ expect(Nokogiri::XML.parse(xml)).to be_xml
60
+ out = parser.render(xml)
61
+ expect(Nokogiri::XML.parse(out)).to be_xml
62
+ end
63
+
64
+ it 'should accept non-ascii characters' do
65
+ data[:teacher] = '老师'
66
+ out = parser.render(xml)
67
+ expect(out).to include('老师')
68
+ expect(Nokogiri::XML.parse(out)).to be_xml
69
+ end
70
+
71
+ it 'should escape as necessary invalid xml characters, if told to' do
72
+ data[:building] = '23rd & A #1 floor'
73
+ data[:classroom] = '--> 201 <!--'
74
+ data[:roster][0][:name] = '<#Ai & Bo>'
75
+ out = parser.render(xml)
76
+
77
+ expect(Nokogiri::XML.parse(out)).to be_xml
78
+ expect(out).to include('23rd &amp; A #1 floor')
79
+ expect(out).to include('--&gt; 201 &lt;!--')
80
+ expect(out).to include('&lt;#Ai &amp; Bo&gt;')
81
+ end
82
+
83
+ context 'not escape xml' do
84
+ let(:parser) { DocxTemplater::TemplateProcessor.new(data, false) }
85
+ it 'does not escape the xml attributes' do
86
+ data[:building] = '23rd <p>&amp;</p> #1 floor'
87
+ out = parser.render(xml)
88
+ expect(Nokogiri::XML.parse(out)).to be_xml
89
+ expect(out).to include('23rd <p>&amp;</p> #1 floor')
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'unmatched begin and end row templates' do
95
+ it 'should not raise' do
96
+ xml = <<EOF
97
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
98
+ <w:body>
99
+ <w:tbl>
100
+ <w:tr><w:tc>
101
+ <w:p>
102
+ <w:r><w:t>#BEGIN_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
103
+ </w:p>
104
+ </w:tc></w:tr>
105
+ <w:tr><w:tc>
106
+ <w:p>
107
+ <w:r><w:t>#END_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
108
+ </w:p>
109
+ </w:tc></w:tr>
110
+ <w:tr><w:tc>
111
+ <w:p>
112
+ <w:r><w:t>#BEGIN_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
113
+ </w:p>
114
+ </w:tc></w:tr>
115
+ <w:tr><w:tc>
116
+ <w:p>
117
+ <w:r><w:t>#END_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
118
+ </w:p>
119
+ </w:tc></w:tr>
120
+ </w:tbl>
121
+ </w:body>
122
+ </xml>
123
+ EOF
124
+ expect { parser.render(xml) }.to_not raise_error
125
+ end
126
+
127
+ it 'should raise an exception' do
128
+ xml = <<EOF
129
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
130
+ <w:body>
131
+ <w:tbl>
132
+ <w:tr><w:tc>
133
+ <w:p>
134
+ <w:r><w:t>#BEGIN_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
135
+ </w:p>
136
+ </w:tc></w:tr>
137
+ <w:tr><w:tc>
138
+ <w:p>
139
+ <w:r><w:t>#END_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
140
+ </w:p>
141
+ </w:tc></w:tr>
142
+ <w:tr><w:tc>
143
+ <w:p>
144
+ <w:r><w:t>#BEGIN_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
145
+ </w:p>
146
+ </w:tc></w:tr>
147
+ </w:tbl>
148
+ </w:body>
149
+ </xml>
150
+ EOF
151
+ expect { parser.render(xml) }.to raise_error(/#END_ROW:EVENT_REPORTS# nil: true/)
152
+ end
153
+ end
154
+
155
+ it 'should enter no text for a nil value' do
156
+ xml = <<EOF
157
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
158
+ <w:body>
159
+ <w:p>Before.$KEY$After</w:p>
160
+ </w:body>
161
+ </xml>
162
+ EOF
163
+ actual = DocxTemplater::TemplateProcessor.new(key: nil).render(xml)
164
+ expected_xml = <<EOF
165
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
166
+ <w:body>
167
+ <w:p>Before.After</w:p>
168
+ </w:body>
169
+ </xml>
170
+ EOF
171
+ expect(actual).to eq(expected_xml)
172
+ end
173
+
174
+ it 'should replace all simple keys with values' do
175
+ non_array_keys = data.reject { |_, v| v.is_a?(Array) }
176
+ non_array_keys.keys.each do |key|
177
+ expect(xml).to include("$#{key.to_s.upcase}$")
178
+ expect(xml).not_to include(data[key].to_s)
179
+ end
180
+ out = parser.render(xml)
181
+
182
+ non_array_keys.each do |key|
183
+ expect(out).not_to include("$#{key}$")
184
+ expect(out).to include(data[key].to_s)
185
+ end
186
+ end
187
+
188
+ it 'should replace all array keys with values' do
189
+ expect(xml).to include('#BEGIN_ROW:')
190
+ expect(xml).to include('#END_ROW:')
191
+ expect(xml).to include('$EACH:')
192
+
193
+ out = parser.render(xml)
194
+
195
+ expect(out).not_to include('#BEGIN_ROW:')
196
+ expect(out).not_to include('#END_ROW:')
197
+ expect(out).not_to include('$EACH:')
198
+
199
+ [:roster, :event_reports].each do |key|
200
+ data[key].each do |row|
201
+ row.values.map(&:to_s).each do |row_value|
202
+ expect(out).to include(row_value)
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ it 'shold render students names in the same order as the data' do
209
+ out = parser.render(xml)
210
+ expect(out).to include('Sally')
211
+ expect(out).to include('Kumar')
212
+ expect(out.index('Kumar')).to be > out.index('Sally')
213
+ end
214
+
215
+ it 'shold render event reports names in the same order as the data' do
216
+ out = parser.render(xml)
217
+ expect(out).to include('Science Museum Field Trip')
218
+ expect(out).to include('Wilderness Center Retreat')
219
+ expect(out.index('Wilderness Center Retreat')).to be > out.index('Science Museum Field Trip')
220
+ end
221
+
222
+ it 'should render 2-line event reports in same order as docx' do
223
+ event_reports_starting_at = xml.index('#BEGIN_ROW:EVENT_REPORTS#')
224
+ expect(event_reports_starting_at).to be >= 0
225
+ expect(xml.index('$EACH:NAME$', event_reports_starting_at)).to be > event_reports_starting_at
226
+ expect(xml.index('$EACH:NOTES$', event_reports_starting_at)).to be > event_reports_starting_at
227
+ expect(xml.index('$EACH:NOTES$', event_reports_starting_at)).to be > xml.index('$EACH:NAME$', event_reports_starting_at)
228
+
229
+ out = parser.render(xml)
230
+ expect(out.index('PTA sponsored event. Spoke to Astronaut with HAM radio.')).to be > out.index('Science Museum Field Trip')
231
+ end
232
+
233
+ it 'should render sums of input data' do
234
+ expect(xml).to include('#SUM')
235
+ out = parser.render(xml)
236
+ expect(out).not_to include('#SUM')
237
+ expect(out).to include("#{data[:roster].count} Students")
238
+ expect(out).to include("#{data[:event_reports].count} Events")
239
+ end
240
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: template_docx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Andrew Wolter
8
+ - xiajian
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-08-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rubyzip
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 1.1.1
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 1.1.1
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: bundler
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.8'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.8'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rubocop
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ description: 'A Ruby library to template Microsoft Word .docx files. Uses a .docx
99
+ file with keyword tags within "$$" as a template. '
100
+ email:
101
+ - jaw@jawspeak.com
102
+ - jhqy2011@gmail.com
103
+ executables: []
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - ".gitignore"
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - bin/console
113
+ - bin/docx-templater
114
+ - bin/setup
115
+ - docx_templater.gemspec
116
+ - lib/docx_templater.rb
117
+ - lib/docx_templater/command.rb
118
+ - lib/docx_templater/docx_creator.rb
119
+ - lib/docx_templater/template_processor.rb
120
+ - lib/docx_templater/version.rb
121
+ - spec/example_input/ExampleBrand.docx
122
+ - spec/example_input/ExampleTemplate.docx
123
+ - spec/example_input/tmp/IntegrationTestOutput.docx
124
+ - spec/example_input/word/document.xml
125
+ - spec/integration_spec.rb
126
+ - spec/spec_helper.rb
127
+ - spec/template_processor_spec.rb
128
+ homepage: https://github.com/Yundianjia/ruby-docx-templater
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: 1.9.3
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.6.6
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Generates new Word .docx files based on a template file.
152
+ test_files:
153
+ - spec/example_input/ExampleBrand.docx
154
+ - spec/example_input/ExampleTemplate.docx
155
+ - spec/example_input/tmp/IntegrationTestOutput.docx
156
+ - spec/example_input/word/document.xml
157
+ - spec/integration_spec.rb
158
+ - spec/spec_helper.rb
159
+ - spec/template_processor_spec.rb