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 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