lm_docstache 2.1.2 → 3.0.4

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