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.
- checksums.yaml +7 -0
- data/.gitignore +53 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +176 -0
- data/README.md +47 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/docx-templater +55 -0
- data/bin/setup +7 -0
- data/docx_templater.gemspec +37 -0
- data/lib/docx_templater.rb +24 -0
- data/lib/docx_templater/command.rb +60 -0
- data/lib/docx_templater/docx_creator.rb +39 -0
- data/lib/docx_templater/template_processor.rb +84 -0
- data/lib/docx_templater/version.rb +3 -0
- data/spec/example_input/ExampleBrand.docx +0 -0
- data/spec/example_input/ExampleTemplate.docx +0 -0
- data/spec/example_input/tmp/IntegrationTestOutput.docx +0 -0
- data/spec/example_input/word/document.xml +1122 -0
- data/spec/integration_spec.rb +49 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/template_processor_spec.rb +240 -0
- metadata +159 -0
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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 & A #1 floor')
|
79
|
+
expect(out).to include('--> 201 <!--')
|
80
|
+
expect(out).to include('<#Ai & Bo>')
|
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>&</p> #1 floor'
|
87
|
+
out = parser.render(xml)
|
88
|
+
expect(Nokogiri::XML.parse(out)).to be_xml
|
89
|
+
expect(out).to include('23rd <p>&</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
|