lm_docstache 2.1.2 → 3.0.4

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: f57ee42bd7804fc41051ca123be801e909acfee0b0406a5eac91e8f562827f11
4
- data.tar.gz: 21ab27cac4e621eb3d3c391b71945a9f29dac1343d790ccb4026a191dbee62ae
3
+ metadata.gz: ce8fea2c12829636bd22622e1c022cf2ead4ec09997b7e13322f2b94b4261654
4
+ data.tar.gz: a25d02153cb1a53bf74111dc59710d83a133a821c4632607c60e06531b28b4ad
5
5
  SHA512:
6
- metadata.gz: 642af591848de049083886277afa1fcd082cf150785d7610ebc15ff328ddc53527bc373e4ab18f910eabffe16b747d8f2191332a968b9e5716a52b4a2e5b2a62
7
- data.tar.gz: e5d7b66bdb616221ccb789841ce6d84eb7ce76b44c2319ebf522c0f8cd4fb53069fbe93df745724c34ec450884b7b462932624dc2bc10a85216dd1c655bf00d5
6
+ metadata.gz: 99bcdac9eea8b1d62e0733b692c600be5e8dcb88710f58d22299b6daa7eabab5f5b01eeb83e61668709655869f6580e918ef38d11bb5ae781db4462b38abba65
7
+ data.tar.gz: ea96603b65d984edfeeb9aa9f9b760c9665af2c5df163e4c3dae076b24ebc10fccb8e1bdee1cc6e01761496a4b89b7454e80965d6a24e96eb67b53b17e46be84
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.4
4
+ * Allow replacement `data` argument to be an `Array`. This feature allow to replace blocks
5
+ in a sequentially order following the sequence of matching blocks order.
6
+
7
+ ## 3.0.3
8
+
9
+ ### Bugfix
10
+
11
+ * Hide custom tags arguments was pushing blocks tags to end of paragraph. There are cases this approach
12
+ doesn't work. I changed to be an ordered replacement when we match hide tags.
13
+ * Avoid to merge Tab tags on fix errors methods. This was causing unexpected document changes.
14
+
15
+ ## 3.0.2
16
+
17
+ ### Bugfix
18
+
19
+ * Fix replacing tags related to hidden custom tags regexp formats. E.g. tab characters.
20
+
21
+ ## 3.0.1
22
+
23
+ ### Bugfix
24
+
25
+ * Fix Hide Custom Tag feature when document there is no text inside a w|r we
26
+ can't split content.
27
+
28
+ ## 3.0.0
29
+
30
+ ## Breaking Changes
31
+ * Replaced Renderer `hide_custom_tags` options to be a `Hash` instead of `Array`.
32
+ There are are edge cases which we want to replace the content on hide custom tags.
33
+ All documentations can be followed on `Renderer` and `HideCustomTag` classes.
34
+
3
35
  ## 2.1.2
4
36
 
5
37
  ### Bugfix
@@ -138,6 +138,10 @@ module LMDocstache
138
138
  previous_text_node = previous_run_node.at_css('w|t')
139
139
  current_text_node = run_node.at_css('w|t')
140
140
 
141
+ # avoid to merge blocks with tabs
142
+ next if run_node.at_css('w|tab')
143
+ next if previous_run_node.at_css('w|tab')
144
+
141
145
  next if style_html != previous_style_html
142
146
  next if current_text_node.nil? || previous_text_node.nil?
143
147
 
@@ -2,43 +2,56 @@ module LMDocstache
2
2
  class HideCustomTags
3
3
  attr_reader :document, :hide_custom_tags
4
4
 
5
- # The +hide_custom_tags+ options is an +Array+ of +Regexp+ or +String+ representing
6
- # the pattern you expect to keep at the document but with white font color.
5
+ # The +hide_custom_tags+ options is a +Hash+ of +Regexp+ or +String+ keys representing
6
+ # the pattern you expect to keep at the document but replacing the content to use
7
+ # font color equal to document background color or white.
8
+ # For the +Hash+ values we can have:
7
9
  #
8
- # You have to remember is not acceptable to have capture groups in your +Regexp's+.
9
- # We don't accept because we need to find all parts of your text, split it in multiple runs
10
- # and add document background color or white font color to matching custom tags.
11
- def initialize(document:, hide_custom_tags: [])
10
+ # * +false+ -> In this case we don't change the text content.
11
+ # * +Proc+ -> When a +Proc+ instance is provided, it's expected it to be
12
+ # able to receive the matched string and to return the string that will be
13
+ # used as replacement.
14
+ # * any other value that will be turned into a string -> in this case, this
15
+ # will be the value that will replace the matched string
16
+ def initialize(document:, hide_custom_tags: {})
12
17
  @document = document
13
18
  @hide_custom_tags = hide_custom_tags
14
19
  end
15
20
 
16
21
  # Find all run nodes matching hide custom tags +Regexp's+ options you defined, split it
17
22
  # in multiple runs and replace font color to document background color or white in the matching tag run node.
23
+ # Replace content if you have defined any replacement value.
18
24
  def hide_custom_tags!
19
- hide_custom_tags.each do |full_pattern|
25
+ hide_custom_tags.each do |full_pattern, value|
20
26
  paragraphs = document.css('w|p')
21
27
  while paragraph = paragraphs.shift do
22
28
  next unless paragraph.text =~ full_pattern
23
29
  run_nodes = paragraph.css('w|r')
24
30
  while run_node = run_nodes.shift
25
- next if run_node.text.to_s.strip.size == 0
26
- remainder_run_node = run_node.clone
27
- run_node.unlink
28
- tag_contents = split_tag_content(remainder_run_node.text, full_pattern)
31
+ next unless run_node.at_css('w|t')
32
+ next unless run_node.text =~ full_pattern
33
+ tag_contents = split_tag_content(run_node.text, full_pattern)
34
+ replacement_nodes = []
29
35
  tag_contents[:content_list].each_with_index do |content, idx|
36
+ remainder_run_node = run_node.clone
30
37
  replace_content(remainder_run_node, content)
31
- run_node_with_match = remainder_run_node.dup
32
38
  matched_tag = tag_contents[:matched_tags][idx]
33
- nodes_list = [remainder_run_node]
39
+ replacement_nodes << remainder_run_node
34
40
  if matched_tag
41
+ run_node_with_match = run_node.clone
35
42
  replace_style(run_node_with_match)
36
- replace_content(run_node_with_match, matched_tag)
37
- nodes_list << run_node_with_match
43
+ matched_content = matched_tag
44
+ if value
45
+ matched_content = value.is_a?(Proc) ?
46
+ value.call(matched_tag) :
47
+ value.to_s
48
+ end
49
+ replace_content(run_node_with_match, matched_content)
50
+ replacement_nodes << run_node_with_match
38
51
  end
39
- paragraph << Nokogiri::XML::NodeSet.new(document, nodes_list)
40
- remainder_run_node = remainder_run_node.clone
41
52
  end
53
+ run_node.add_next_sibling(Nokogiri::XML::NodeSet.new(document, replacement_nodes))
54
+ run_node.unlink
42
55
  end
43
56
  end
44
57
  end
@@ -18,7 +18,22 @@ module LMDocstache
18
18
  VARIABLE_MATCHER = /{{([^#\^\/].*?)}}/
19
19
 
20
20
  attr_reader :document, :data, :blocks, :special_variable_replacements, :hide_custom_tags
21
+ attr_reader :data_sequential_replacement
21
22
 
23
+ # Constructor +data+ argument is a +Hash+ where the key is
24
+ # expected to be a +String+ representing the replacement block value. +Hash+
25
+ # key must not contain the `{{}}` part, but only the pattern characters.
26
+ # As for the values of the +Hash+, we have options:
27
+ #
28
+ # * +String+ will be the value that will replace matching string.
29
+ # * +Array<String>+ will be an ordered sequence of values that will replace the matched string following
30
+ # document matching order.
31
+ #
32
+ # Example:
33
+ # { 'full_name' => 'John Doe', 'text|req|Client' => ['John', 'Matt', 'Paul'] }
34
+ #
35
+ # Constructor +options+ argument is a +Hash+ where keys can be:
36
+ #
22
37
  # The +special_variable_replacements+ option is a +Hash+ where the key is
23
38
  # expected to be either a +Regexp+ or a +String+ representing the pattern
24
39
  # of more specific type of variables that deserves a special treatment. The
@@ -34,22 +49,31 @@ module LMDocstache
34
49
  # * any other value that will be turned into a string -> in this case, this
35
50
  # will be the value that will replace the matched string
36
51
  #
37
- # The +hide_custom_tags+ options is an +Array+ of +Regexp+ or +String+ representing
38
- # the pattern you expect to keep at the document but with white font color.
52
+ # The +hide_custom_tags+ options is a +Hash+ of +Regexp+ or +String+ keys representing
53
+ # the pattern you expect to keep at the document but replacing the content to use
54
+ # font color equal to document background color or white.
55
+ # For the +Hash+ values we can have:
39
56
  #
40
- # You have to remember is not acceptable to have capture groups in your +Regexp's+.
41
- # We don't accept because we need to find all parts of your text, split it in multiple runs
42
- # and add document background color or white font color to matching custom tags.
57
+ # * +false+ -> In this case we don't change the text content.
58
+ # * +Proc+ -> When a +Proc+ instance is provided, it's expected it to be
59
+ # able to receive the matched string and to return the string that will be
60
+ # used as replacement.
61
+ # * any other value that will be turned into a string -> in this case, this
62
+ # will be the value that will replace the matched string
43
63
  def initialize(document, data, options = {})
44
64
  @document = document
45
- @data = data.transform_keys(&:to_s)
46
- @special_variable_replacements = options.fetch(:special_variable_replacements, {})
47
- @hide_custom_tags = load_hide_custom_tags(options)
65
+ @data = data.transform_keys(&:to_s).select {|e, v| !v.is_a?(Array) }
66
+ @data_sequential_replacement = data.transform_keys(&:to_s).select {|e, v| v.is_a?(Array) }
67
+ @special_variable_replacements = add_blocks_to_regexp(options.fetch(:special_variable_replacements, {}))
68
+ @hide_custom_tags = add_blocks_to_regexp(options.fetch(:hide_custom_tags, {}))
48
69
  end
49
70
 
50
- def load_hide_custom_tags(options)
51
- options.fetch(:hide_custom_tags, []).map do |regexp_str|
52
- regexp_str.is_a?(String) ? Regexp.new("{{#{regexp_str}}}") : /{{#{regexp_str.source}}/
71
+ # Replace +Regepx+ or +String+ keys to have the enclosing blocks
72
+ def add_blocks_to_regexp(options)
73
+ options.inject({}) do |x, (regexp_str, value)|
74
+ key = regexp_str.is_a?(String) ? Regexp.new("{{#{regexp_str}}}") : /{{#{regexp_str.source}}/
75
+ x[key] = value
76
+ x
53
77
  end
54
78
  end
55
79
 
@@ -57,6 +81,7 @@ module LMDocstache
57
81
  hide_custom_tags!
58
82
  find_blocks
59
83
  replace_conditional_blocks_in_document!
84
+ replace_data_sequentially_in_document!
60
85
  replace_variables_in_document!
61
86
  end
62
87
 
@@ -128,15 +153,39 @@ module LMDocstache
128
153
  variable_replacement.call($1) :
129
154
  variable_replacement.to_s
130
155
  end
131
-
132
156
  text_node.content = text
133
157
  end
134
158
  end
135
159
 
160
+ def replace_data_sequentially_in_document!
161
+ data_sequential_replacement.each do |tag_key, values|
162
+
163
+ tag = Regexp.escape("{{#{tag_key}}}")
164
+ pattern_found = 0
165
+
166
+ document.css('w|t').each do |text_node|
167
+ text = text_node.text
168
+
169
+ if text.match(tag)
170
+
171
+ text.gsub!(/#{tag}/) do |_match|
172
+ value = values[pattern_found]
173
+ # if there is no more available value replace the content with empty string
174
+ return '' unless value
175
+
176
+ pattern_found +=1
177
+ value
178
+ end
179
+
180
+ text_node.content = text
181
+ end
182
+ end
183
+ end
184
+ end
185
+
136
186
  def has_skippable_variable?(text)
137
- return true if hide_custom_tags.find { |pattern| text =~ pattern }
187
+ return true if hide_custom_tags.find { |(pattern, value)| text =~ pattern }
138
188
  !!special_variable_replacements.find do |(pattern, value)|
139
- pattern = pattern.is_a?(String) ? /{{#{pattern}}}/ : /{{#{pattern.source}}}/
140
189
  text =~ pattern && value == false
141
190
  end
142
191
  end
@@ -144,7 +193,6 @@ module LMDocstache
144
193
  def special_variable_replacement(text)
145
194
  Array(
146
195
  special_variable_replacements.find do |(pattern, value)|
147
- pattern = pattern.is_a?(String) ? /{{#{pattern}}}/ : /{{#{pattern.source}}}/
148
196
  text =~ pattern && !!value
149
197
  end
150
198
  ).last
@@ -6,7 +6,8 @@ module LMDocstache
6
6
 
7
7
  def initialize(xml, data, options = {})
8
8
  @content = xml
9
- @parser = Parser.new(xml, data, options.slice(:special_variable_replacements, :hide_custom_tags))
9
+ option_types = [:special_variable_replacements, :hide_custom_tags]
10
+ @parser = Parser.new(xml, data, options.slice(*option_types))
10
11
  end
11
12
 
12
13
  def render
@@ -1,3 +1,3 @@
1
1
  module LMDocstache
2
- VERSION = "2.1.2"
2
+ VERSION = "3.0.4"
3
3
  end
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe LMDocstache::HideCustomTags do
4
-
5
4
  context '#example' do
6
5
  let(:output_dir) { "#{base_path}/tmp/" }
7
6
  let(:output_file) { File.new("#{output_dir}/BlankTestOutput.docx", 'w') }
@@ -16,10 +15,18 @@ describe LMDocstache::HideCustomTags do
16
15
  end
17
16
 
18
17
  let(:base_path) { SPEC_BASE_PATH.join('example_input') }
19
- let(:document) { LMDocstache::Document.new(input_file).instance_variable_get(:@document) }
20
- let(:regexp_tag) { /{{(?:sig|sigfirm|date|check|text|initial)\|(?:req|noreq)\|.+?}}/ }
18
+ let(:document) {
19
+ doc = LMDocstache::Document.new(input_file)
20
+ doc.fix_errors
21
+ doc.instance_variable_get(:@document)
22
+ }
23
+ let(:regexp_tag) { /(?:sig|sigfirm|date|text|initial)\|(?:req|noreq)\|.+?/ }
24
+ let(:regexp_for_replacement) { /(?:check)\|(?:req|noreq)\|.+?/ }
21
25
  let(:hide_custom_tags) {
22
- LMDocstache::HideCustomTags.new(document: document, hide_custom_tags: [ regexp_tag ])
26
+ LMDocstache::HideCustomTags.new(document: document, hide_custom_tags: {
27
+ /#{regexp_tag}/ => false,
28
+ /#{regexp_for_replacement}/ => 'replaced_content'
29
+ })
23
30
  }
24
31
 
25
32
  context "giving a document with blue background" do
@@ -30,10 +37,9 @@ describe LMDocstache::HideCustomTags do
30
37
  d = hide_custom_tags.document
31
38
  run_nodes = d.css('w|p w|r')
32
39
  while run_node = run_nodes.shift
33
- if run_node.text =~ regexp_tag
34
- expect(run_node.at_css('w|rPr w|color').first[1]).to eq('4472C4')
35
- expect(run_node.children.first.name).to eq('rPr')
36
- end
40
+ next unless run_node.text =~ regexp_tag
41
+ expect(run_node.at_css('w|rPr w|color').first[1]).to eq('4472C4')
42
+ expect(run_node.children.first.name).to eq('rPr')
37
43
  end
38
44
  end
39
45
  end
@@ -46,14 +52,13 @@ describe LMDocstache::HideCustomTags do
46
52
  d = hide_custom_tags.document
47
53
  run_nodes = d.css('w|p w|r')
48
54
  while run_node = run_nodes.shift
49
- if run_node.text =~ regexp_tag
50
- expect(run_node.at_css('w|rPr w|color').first[1]).to eq('FFFFFF')
51
- expect(run_node.children.first.name).to eq('rPr')
52
- end
55
+ next unless run_node.text =~ regexp_tag
56
+ expect(run_node.at_css('w|rPr w|color').first[1]).to eq('FFFFFF')
57
+ expect(run_node.children.first.name).to eq('rPr')
53
58
  end
54
59
  end
55
60
  end
56
- context 'giving a document without rpr' do
61
+ context 'giving a document without rpr and block tags on the left' do
57
62
  let(:input_file) { "#{base_path}/docx-no-rpr.docx" }
58
63
 
59
64
  it 'expect to have a white color on all hide custom tags matching and have first child node equal rPr tag' do
@@ -61,12 +66,33 @@ describe LMDocstache::HideCustomTags do
61
66
  d = hide_custom_tags.document
62
67
  run_nodes = d.css('w|p w|r')
63
68
  while run_node = run_nodes.shift
64
- if run_node.text =~ regexp_tag
65
- expect(run_node.at_css('w|rPr w|color').first[1]).to eq('FFFFFF')
66
- expect(run_node.children.first.name).to eq('rPr')
67
- end
69
+ next unless run_node.text =~ regexp_tag
70
+ expect(run_node.at_css('w|rPr w|color').first[1]).to eq('FFFFFF')
71
+ expect(run_node.children.first.name).to eq('rPr')
68
72
  end
69
73
  end
74
+ it 'expect to have a white color on all replacement tags and content following replacement' do
75
+ hide_custom_tags.hide_custom_tags!
76
+ d = hide_custom_tags.document
77
+ run_nodes = d.css('w|p w|r')
78
+ total_replacement = 0
79
+ while run_node = run_nodes.shift
80
+ next unless run_node.text =~ /replaced_content/
81
+ total_replacement+=1
82
+ expect(run_node.at_css('w|rPr w|color').first[1]).to eq('FFFFFF')
83
+ expect(run_node.children.first.name).to eq('rPr')
84
+ end
85
+ expect(total_replacement).to eq(2)
86
+ end
87
+ end
88
+
89
+ context 'giving a document with tabs spacing in the middle of replacement tags' do
90
+ let(:input_file) { "#{base_path}/sample-signature-with-tabs-spacing.docx" }
91
+ it 'expect to not replace tabs' do
92
+ hide_custom_tags.hide_custom_tags!
93
+ d = hide_custom_tags.document
94
+ expect(d.css('w|p w|tab').size).to eq(11)
95
+ end
70
96
  end
71
97
  end
72
98
  end
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'securerandom'
2
3
  require 'active_support/core_ext/object/blank.rb'
3
4
 
4
5
  module LMDocstache
@@ -139,5 +140,30 @@ describe 'integration test', integration: true do
139
140
  expect(output).to include('<w:t xml:space="preserve">Test Multiple text in the same line </w:t>')
140
141
  end
141
142
  end
143
+
144
+ context "yoooo" do
145
+ let(:input_file) { "#{base_path}/multi_o.docx" }
146
+ let(:render_options) {
147
+ {
148
+ special_variable_replacements: { "(date|sig|sigfirm|text|check|initial|initials)\\|(req|noreq)\\|(.+?)" => false }.freeze,
149
+ hide_custom_tags: ['(?:sig|sigfirm|date|check|text|initial)\|(?:req|noreq)\|.+?']
150
+ }
151
+ }
152
+ let(:document) { LMDocstache::Document.new(input_file) }
153
+
154
+ it 'should have content replacement aligned with hide custom tags' do
155
+ doc = document
156
+ doc.fix_errors
157
+ new_file_path = "#{Time.now.to_i}-#{SecureRandom.uuid}.docx"
158
+ n = doc.render_file(new_file_path, { 'full_name' => 'fred document01' }, render_options)
159
+ noko = doc.render_xml({ 'full_name' => 'fred document01' }, render_options)
160
+ output = noko['word/document.xml'].to_xml
161
+ #puts output
162
+ #doc.render_file(new_file_path, { 'full_name' => 'fred document01' }, render_options)
163
+ #noko = doc.render_xml({ 'full_name' => 'fred document01' }, render_options)
164
+ #output = noko['word/document.xml'].to_xml
165
+ #puts output
166
+ end
167
+ end
142
168
  end
143
169
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lm_docstache
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 3.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roey Chasman
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2021-03-22 00:00:00.000000000 Z
15
+ date: 2021-05-14 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: nokogiri
@@ -123,6 +123,7 @@ files:
123
123
  - spec/example_input/blank.docx
124
124
  - spec/example_input/docx-no-rpr.docx
125
125
  - spec/example_input/sample-signature-blue.docx
126
+ - spec/example_input/sample-signature-with-tabs-spacing.docx
126
127
  - spec/example_input/sample-signature.docx
127
128
  - spec/hide_custom_tags_spec.rb
128
129
  - spec/integration_spec.rb
@@ -157,6 +158,7 @@ test_files:
157
158
  - spec/example_input/blank.docx
158
159
  - spec/example_input/docx-no-rpr.docx
159
160
  - spec/example_input/sample-signature-blue.docx
161
+ - spec/example_input/sample-signature-with-tabs-spacing.docx
160
162
  - spec/example_input/sample-signature.docx
161
163
  - spec/hide_custom_tags_spec.rb
162
164
  - spec/integration_spec.rb