lm_docstache 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4da1923074bce295e621bf0fff86882e16fe9f8bef9bf7373e955ce4b1a1f8f7
4
- data.tar.gz: 7fb1709820b0d692c68b0ca8f5b2cea3769170e2387254f309bc77cd2cb9c730
3
+ metadata.gz: b345230c53f335f5de05dccdaca946bf30edaee21031c219dbdad1315c61c915
4
+ data.tar.gz: 554faf30a6988614877f20d72d172d1a66c11bcb2a145f4eb4efd45f7a598fb0
5
5
  SHA512:
6
- metadata.gz: b4d8662856ee7a9b1239b4ca5e1427ae57132b0da6133b7cc955e85966c90facfa20da9261b6d4654a5fa2b96fe5bedf2dd61ea20d70039a276aa9628d412223
7
- data.tar.gz: 65912e5b0715e750de569d69b3da542e1f7ebbe58a58cab115b8090e64de4e8b329b18f7c95e45bc2a234a15045728acab13611303889ba7dde923f54830c86d
6
+ metadata.gz: 66732e83ef4cbd515c10904dd293a29346bb1278f1bcfa3ad7becc6e027798f8ca2ca1534f2a9ffc86ee1e4db2d632cd6c926fd703975f386524fd42ccd48516
7
+ data.tar.gz: f3ff05f0d5e5b55e3c6eb3e070713b625f74544d667816a71a761d48521ffda12146ff72b3a1189b9a794b8a190a60ea7227a374c0c58711471fba1786de0ce4
data/.gitignore CHANGED
@@ -2,4 +2,3 @@
2
2
  spec/example_input/tmp
3
3
  .idea/
4
4
  *.gem
5
- Gemfile.lock
@@ -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
- Fork the Docstache codebase and rename all references to LMDocstache.
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
@@ -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
@@ -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
- find_all(name: name, data: data, elements: match.elements, inverted: inverted, condition: condition)
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
- expand_and_replace_block(block) if block.present?
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
- case condition = @data.get(block.name, condition: block.condition)
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
@@ -1,3 +1,3 @@
1
1
  module LMDocstache
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -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
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.1.2
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-06-09 00:00:00.000000000 Z
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