lm_docstache 1.1.2 → 1.2.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 +4 -4
- data/.gitignore +0 -1
- data/CHANGELOG.md +5 -1
- data/Gemfile.lock +48 -0
- data/lib/lm_docstache/block.rb +13 -4
- data/lib/lm_docstache/document.rb +20 -0
- data/lib/lm_docstache/renderer.rb +64 -8
- data/lib/lm_docstache/version.rb +1 -1
- data/lm_docstache.gemspec +2 -2
- data/spec/conditional_block_spec.rb +82 -0
- data/spec/example_input/blank.docx +0 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b345230c53f335f5de05dccdaca946bf30edaee21031c219dbdad1315c61c915
|
4
|
+
data.tar.gz: 554faf30a6988614877f20d72d172d1a66c11bcb2a145f4eb4efd45f7a598fb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66732e83ef4cbd515c10904dd293a29346bb1278f1bcfa3ad7becc6e027798f8ca2ca1534f2a9ffc86ee1e4db2d632cd6c926fd703975f386524fd42ccd48516
|
7
|
+
data.tar.gz: f3ff05f0d5e5b55e3c6eb3e070713b625f74544d667816a71a761d48521ffda12146ff72b3a1189b9a794b8a190a60ea7227a374c0c58711471fba1786de0ce4
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.2.0
|
4
|
+
* Add support for inline conditionals.
|
5
|
+
* Add some tests.
|
6
|
+
|
3
7
|
## 1.0.0
|
4
|
-
|
8
|
+
* Fork the Docstache codebase and rename all references to LMDocstache.
|
5
9
|
|
6
10
|
## 0.3.2
|
7
11
|
* [b3b66a0cd7ae67834cdbe7c18cefdb1498bcc5ab] I'm an idiot. Fixed an error in the `Document#unusable_tags` method
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
lm_docstache (1.2.0)
|
5
|
+
nokogiri (~> 1.6)
|
6
|
+
rubyzip (~> 1.1)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
byebug (11.1.3)
|
12
|
+
coderay (1.1.3)
|
13
|
+
diff-lcs (1.4.4)
|
14
|
+
method_source (1.0.0)
|
15
|
+
mini_portile2 (2.4.0)
|
16
|
+
nokogiri (1.10.10)
|
17
|
+
mini_portile2 (~> 2.4.0)
|
18
|
+
pry (0.13.1)
|
19
|
+
coderay (~> 1.1)
|
20
|
+
method_source (~> 1.0)
|
21
|
+
pry-byebug (3.9.0)
|
22
|
+
byebug (~> 11.0)
|
23
|
+
pry (~> 0.13.0)
|
24
|
+
rspec (3.9.0)
|
25
|
+
rspec-core (~> 3.9.0)
|
26
|
+
rspec-expectations (~> 3.9.0)
|
27
|
+
rspec-mocks (~> 3.9.0)
|
28
|
+
rspec-core (3.9.2)
|
29
|
+
rspec-support (~> 3.9.3)
|
30
|
+
rspec-expectations (3.9.2)
|
31
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
+
rspec-support (~> 3.9.0)
|
33
|
+
rspec-mocks (3.9.1)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.9.0)
|
36
|
+
rspec-support (3.9.3)
|
37
|
+
rubyzip (1.3.0)
|
38
|
+
|
39
|
+
PLATFORMS
|
40
|
+
ruby
|
41
|
+
|
42
|
+
DEPENDENCIES
|
43
|
+
lm_docstache!
|
44
|
+
pry-byebug (>= 1)
|
45
|
+
rspec (>= 3.1.0)
|
46
|
+
|
47
|
+
BUNDLED WITH
|
48
|
+
1.17.2
|
data/lib/lm_docstache/block.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module LMDocstache
|
2
2
|
class Block
|
3
|
-
attr_reader :name, :opening_element, :content_elements, :closing_element, :inverted, :condition
|
4
|
-
def initialize(name:, data:, opening_element:, content_elements:, closing_element:, inverted:, condition: nil)
|
3
|
+
attr_reader :name, :opening_element, :content_elements, :closing_element, :inverted, :condition, :inline
|
4
|
+
def initialize(name:, data:, opening_element:, content_elements:, closing_element:, inverted:, condition: nil, inline: false)
|
5
5
|
@name = name
|
6
6
|
@data = data
|
7
7
|
@opening_element = opening_element
|
@@ -9,6 +9,7 @@ module LMDocstache
|
|
9
9
|
@closing_element = closing_element
|
10
10
|
@inverted = inverted
|
11
11
|
@condition = condition
|
12
|
+
@inline = inline
|
12
13
|
end
|
13
14
|
|
14
15
|
def type
|
@@ -31,7 +32,7 @@ module LMDocstache
|
|
31
32
|
type == :conditional
|
32
33
|
end
|
33
34
|
|
34
|
-
def self.find_all(name:, data:, elements:, inverted:, condition: nil, ignore_missing: true)
|
35
|
+
def self.find_all(name:, data:, elements:, inverted:, condition: nil, ignore_missing: true, child: false)
|
35
36
|
inverted_op = inverted ? '\^' : '\#'
|
36
37
|
full_tag_regex = /\{\{#{inverted_op}(#{name})\s?#{condition}\}\}.+?\{\{\/\k<1>\}\}/m
|
37
38
|
start_tag_regex = /\{\{#{inverted_op}#{name}\s?#{condition}\}\}/m
|
@@ -41,7 +42,11 @@ module LMDocstache
|
|
41
42
|
if elements.any? { |e| e.text.match(full_tag_regex) }
|
42
43
|
matches = elements.select { |e| e.text.match(full_tag_regex) }
|
43
44
|
return matches.flat_map do |match|
|
44
|
-
|
45
|
+
if match.elements.any?
|
46
|
+
find_all(name: name, data: data, elements: match.elements, inverted: inverted, condition: condition, child: true)
|
47
|
+
else
|
48
|
+
extract_block_from_element(name: name, data: data, element: match, inverted: inverted, condition: condition)
|
49
|
+
end
|
45
50
|
end
|
46
51
|
else
|
47
52
|
opening = elements.find { |e| e.text.match(start_tag_regex) }
|
@@ -58,5 +63,9 @@ module LMDocstache
|
|
58
63
|
raise "Block not found in given elements" unless ignore_missing
|
59
64
|
end
|
60
65
|
end
|
66
|
+
|
67
|
+
def self.extract_block_from_element(name: name, data: data, element: element, inverted: inverted, condition: condition)
|
68
|
+
return Block.new(name: name, data: data, opening_element: element.parent.previous, content_elements: [element.parent], closing_element: element.parent.next, inverted: inverted, condition: condition, inline: true)
|
69
|
+
end
|
61
70
|
end
|
62
71
|
end
|
@@ -68,6 +68,16 @@ module LMDocstache
|
|
68
68
|
File.open(output, "w") { |f| f.write buffer.string }
|
69
69
|
end
|
70
70
|
|
71
|
+
def render_replace(output, text)
|
72
|
+
rendered_documents = Hash[
|
73
|
+
@documents.map do |(path, document)|
|
74
|
+
[path, LMDocstache::Renderer.new(document.dup, {}).render_replace(text)]
|
75
|
+
end
|
76
|
+
]
|
77
|
+
buffer = zip_buffer(rendered_documents)
|
78
|
+
File.open(output, "w") { |f| f.write buffer.string }
|
79
|
+
end
|
80
|
+
|
71
81
|
def render_stream(data={})
|
72
82
|
rendered_documents = Hash[
|
73
83
|
@documents.map do |(path, document)|
|
@@ -79,6 +89,16 @@ module LMDocstache
|
|
79
89
|
return buffer.sysread
|
80
90
|
end
|
81
91
|
|
92
|
+
def render_xml(data={})
|
93
|
+
rendered_documents = Hash[
|
94
|
+
@documents.map do |(path, document)|
|
95
|
+
[path, LMDocstache::Renderer.new(document.dup, data).render]
|
96
|
+
end
|
97
|
+
]
|
98
|
+
|
99
|
+
rendered_documents
|
100
|
+
end
|
101
|
+
|
82
102
|
private
|
83
103
|
|
84
104
|
def problem_paragraphs
|
@@ -13,6 +13,16 @@ module LMDocstache
|
|
13
13
|
return @content
|
14
14
|
end
|
15
15
|
|
16
|
+
def render_replace(text)
|
17
|
+
@content.css('w|t').each do |text_el|
|
18
|
+
rendered_string = text_el.text
|
19
|
+
if !(results = rendered_string.scan(/\|-Lawmatics Test-\|/)).empty?
|
20
|
+
text_el.content = text
|
21
|
+
end
|
22
|
+
end
|
23
|
+
return @content
|
24
|
+
end
|
25
|
+
|
16
26
|
private
|
17
27
|
|
18
28
|
def find_and_expand_blocks
|
@@ -22,20 +32,18 @@ module LMDocstache
|
|
22
32
|
Block.find_all(name: block[1], elements: @content.elements, data: @data, inverted: inverted, condition: block[2])
|
23
33
|
end
|
24
34
|
found_blocks.each do |block|
|
25
|
-
|
35
|
+
if block.inline
|
36
|
+
replace_conditionals(block)
|
37
|
+
else
|
38
|
+
expand_and_replace_block(block) if block.present?
|
39
|
+
end
|
26
40
|
end
|
27
41
|
end
|
28
42
|
|
29
43
|
def expand_and_replace_block(block)
|
30
44
|
case block.type
|
31
45
|
when :conditional
|
32
|
-
|
33
|
-
when Array
|
34
|
-
condition = !condition.empty?
|
35
|
-
else
|
36
|
-
condition = !!condition
|
37
|
-
end
|
38
|
-
condition = !condition if block.inverted
|
46
|
+
condition = get_condition(block)
|
39
47
|
unless condition
|
40
48
|
block.content_elements.each(&:unlink)
|
41
49
|
end
|
@@ -60,6 +68,40 @@ module LMDocstache
|
|
60
68
|
block.closing_element.unlink
|
61
69
|
end
|
62
70
|
|
71
|
+
def replace_conditionals(block)
|
72
|
+
condition = get_condition(block)
|
73
|
+
|
74
|
+
@content.css('w|t').each do |text_el|
|
75
|
+
rendered_string = text_el.text
|
76
|
+
|
77
|
+
if !(results = rendered_string.scan(/{{#(.*?)}}(.*?){{\/(.*?)}}/)).empty?
|
78
|
+
results.each do |r|
|
79
|
+
if condition
|
80
|
+
rendered_string.sub!("{{##{r[0]}}}", "")
|
81
|
+
rendered_string.sub!("{{/#{r[2]}}}", "")
|
82
|
+
else
|
83
|
+
rendered_string.sub!("{{##{r[0]}}}#{r[1]}{{/#{r[2]}}}", "")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# the only difference in this code block is caret instead of pound in three places,
|
89
|
+
# and the condition being inverted. maybe combine them?
|
90
|
+
if !(results = rendered_string.scan(/{{\^(.*?)}}(.*?){{\/(.*?)}}/)).empty?
|
91
|
+
results.each do |r|
|
92
|
+
if !condition
|
93
|
+
rendered_string.sub!("{{^#{r[0]}}}", "")
|
94
|
+
rendered_string.sub!("{{/#{r[2]}}}", "")
|
95
|
+
else
|
96
|
+
rendered_string.sub!("{{^#{r[0]}}}#{r[1]}{{/#{r[2]}}}", "")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
text_el.content = rendered_string
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
63
105
|
def replace_tags(elements, data)
|
64
106
|
elements.css('w|t').each do |text_el|
|
65
107
|
if !(results = text_el.text.scan(/\{\{([\w\.]+)\}\}/).flatten).empty?
|
@@ -72,5 +114,19 @@ module LMDocstache
|
|
72
114
|
end
|
73
115
|
return elements
|
74
116
|
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def get_condition(block)
|
121
|
+
case condition = @data.get(block.name, condition: block.condition)
|
122
|
+
when Array
|
123
|
+
condition = !condition.empty?
|
124
|
+
else
|
125
|
+
condition = !!condition
|
126
|
+
end
|
127
|
+
condition = !condition if block.inverted
|
128
|
+
|
129
|
+
condition
|
130
|
+
end
|
75
131
|
end
|
76
132
|
end
|
data/lib/lm_docstache/version.rb
CHANGED
data/lm_docstache.gemspec
CHANGED
@@ -5,8 +5,8 @@ require "lm_docstache/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "lm_docstache"
|
7
7
|
s.version = LMDocstache::VERSION
|
8
|
-
s.authors = ["Roey Chasman", "Will Cosgrove"]
|
9
|
-
s.email = ["roey@lawmatics.com", "will@willcosgrove.com"]
|
8
|
+
s.authors = ["Roey Chasman", "Jonathan Stevens", "Will Cosgrove"]
|
9
|
+
s.email = ["roey@lawmatics.com", "jonathan@lawmatics.com", "will@willcosgrove.com"]
|
10
10
|
s.homepage = "https://www.lawmatics.com"
|
11
11
|
s.summary = %q{Merges Hash of Data into Word docx template files using mustache syntax}
|
12
12
|
s.description = %q{Integrates data into MS Word docx template files. Processing supports loops and replacement of strings of data both outside and within loops.}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module LMDocstache
|
5
|
+
module TestData
|
6
|
+
DATA = {
|
7
|
+
gender: 'Male',
|
8
|
+
first_name: 'Hector',
|
9
|
+
last_name: 'Jones'
|
10
|
+
}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe LMDocstache::Renderer do
|
15
|
+
let(:data) { Marshal.load(Marshal.dump(LMDocstache::TestData::DATA)) } # deep copy
|
16
|
+
let(:base_path) { SPEC_BASE_PATH.join('example_input') }
|
17
|
+
let(:blank_doc_path) { "#{base_path}/blank.docx" }
|
18
|
+
let(:blank_doc) { LMDocstache::Document.new(blank_doc_path) }
|
19
|
+
let(:output_dir) { "#{base_path}/tmp" }
|
20
|
+
let(:temp_file) { "#{output_dir}/temp.docx" }
|
21
|
+
let(:result_file) { "#{output_dir}/result.docx" }
|
22
|
+
|
23
|
+
def render_docx(doc_text)
|
24
|
+
# create doc from blank
|
25
|
+
blank_doc.render_replace(temp_file, doc_text)
|
26
|
+
|
27
|
+
doc = LMDocstache::Document.new(temp_file).render_file(result_file, data)
|
28
|
+
|
29
|
+
result_doc = LMDocstache::Document.new(result_file).render_xml(data)
|
30
|
+
result_doc["word/document.xml"].text
|
31
|
+
end
|
32
|
+
|
33
|
+
before do
|
34
|
+
FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
|
35
|
+
Dir.mkdir(output_dir)
|
36
|
+
end
|
37
|
+
|
38
|
+
after do
|
39
|
+
File.delete(temp_file)
|
40
|
+
File.delete(result_file)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should handle inline conditional tags' do
|
44
|
+
result_text = render_docx("Refer to the matter as {{#gender == Male}}he{{/gender}}{{^gender == Male}}she{{/gender}} please")
|
45
|
+
expected_text = "Refer to the matter as he please"
|
46
|
+
expect(result_text).to eq(expected_text)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should handle else statements with inline conditional tags' do
|
50
|
+
result_text = render_docx("Refer to the matter as {{#gender == 'Female'}}he{{/gender}}{{^gender == 'Female'}}she{{/gender}} please")
|
51
|
+
expected_text = "Refer to the matter as she please"
|
52
|
+
expect(result_text).to eq(expected_text)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should handle inline conditional tags with no matches' do
|
56
|
+
result_text = render_docx("Refer to the matter as {{#gender == 'none'}}he{{/gender}} please")
|
57
|
+
expected_text = "Refer to the matter as please"
|
58
|
+
expect(result_text).to eq(expected_text)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should handle inline conditional tags with tags inside' do
|
62
|
+
result_text = render_docx("Refer to the matter as {{#gender == 'Male'}}{{first_name}}{{/gender}}{{^gender == 'Male'}}{{last_name}}{{/gender}} please")
|
63
|
+
expected_text = "Refer to the matter as Hector please"
|
64
|
+
expect(result_text).to eq(expected_text)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should handle multiline conditional tags' do
|
68
|
+
text = [
|
69
|
+
"Refer to the matter as",
|
70
|
+
"{{#gender == 'Male'}}",
|
71
|
+
"{{first_name}}",
|
72
|
+
"{{/gender}}",
|
73
|
+
"{{^gender == 'Male'}}",
|
74
|
+
"{{last_name}}",
|
75
|
+
"{{/gender}}Thank you"
|
76
|
+
].join("\r\n")
|
77
|
+
|
78
|
+
result_text = render_docx(text)
|
79
|
+
expected_text = "Refer to the matter as\r\rHector\r\rThank you"
|
80
|
+
expect(result_text).to eq(expected_text)
|
81
|
+
end
|
82
|
+
end
|
Binary file
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lm_docstache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roey Chasman
|
8
|
+
- Jonathan Stevens
|
8
9
|
- Will Cosgrove
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2020-
|
13
|
+
date: 2020-07-15 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: nokogiri
|
@@ -71,6 +72,7 @@ description: Integrates data into MS Word docx template files. Processing suppor
|
|
71
72
|
loops and replacement of strings of data both outside and within loops.
|
72
73
|
email:
|
73
74
|
- roey@lawmatics.com
|
75
|
+
- jonathan@lawmatics.com
|
74
76
|
- will@willcosgrove.com
|
75
77
|
executables: []
|
76
78
|
extensions: []
|
@@ -79,6 +81,7 @@ files:
|
|
79
81
|
- ".gitignore"
|
80
82
|
- CHANGELOG.md
|
81
83
|
- Gemfile
|
84
|
+
- Gemfile.lock
|
82
85
|
- LICENSE.txt
|
83
86
|
- README.md
|
84
87
|
- Rakefile
|
@@ -89,9 +92,11 @@ files:
|
|
89
92
|
- lib/lm_docstache/renderer.rb
|
90
93
|
- lib/lm_docstache/version.rb
|
91
94
|
- lm_docstache.gemspec
|
95
|
+
- spec/conditional_block_spec.rb
|
92
96
|
- spec/data_scope_spec.rb
|
93
97
|
- spec/empty_data_scope_spec.rb
|
94
98
|
- spec/example_input/ExampleTemplate.docx
|
99
|
+
- spec/example_input/blank.docx
|
95
100
|
- spec/example_input/word/document.xml
|
96
101
|
- spec/integration_spec.rb
|
97
102
|
- spec/spec_helper.rb
|
@@ -120,9 +125,11 @@ signing_key:
|
|
120
125
|
specification_version: 4
|
121
126
|
summary: Merges Hash of Data into Word docx template files using mustache syntax
|
122
127
|
test_files:
|
128
|
+
- spec/conditional_block_spec.rb
|
123
129
|
- spec/data_scope_spec.rb
|
124
130
|
- spec/empty_data_scope_spec.rb
|
125
131
|
- spec/example_input/ExampleTemplate.docx
|
132
|
+
- spec/example_input/blank.docx
|
126
133
|
- spec/example_input/word/document.xml
|
127
134
|
- spec/integration_spec.rb
|
128
135
|
- spec/spec_helper.rb
|