docstache 0.0.1
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 +5 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +176 -0
- data/README.rdoc +24 -0
- data/Rakefile +7 -0
- data/docstache.gemspec +22 -0
- data/lib/docstache/document.rb +102 -0
- data/lib/docstache/renderer.rb +152 -0
- data/lib/docstache/version.rb +3 -0
- data/lib/docstache.rb +9 -0
- data/spec/example_input/ExampleTemplate.docx +0 -0
- data/spec/example_input/word/document.xml +1122 -0
- data/spec/integration_spec.rb +40 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/template_processor_spec.rb +222 -0
- metadata +94 -0
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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 & A #1 floor')
|
61
|
+
expect(out).to include('--> 201 <!--')
|
62
|
+
expect(out).to include('<#Ai & Bo>')
|
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>&</p> #1 floor'
|
69
|
+
out = parser.render(xml)
|
70
|
+
expect(Nokogiri::XML.parse(out)).to be_xml
|
71
|
+
expect(out).to include('23rd <p>&</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:
|