template_docx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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