lm_docstache 3.0.2 → 3.0.7

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: e8db9e1f7f1a534059ed080bfd89005372b334cc203b66cfbb45323b8116b980
4
- data.tar.gz: 4af011e633d34a62fcc0ed24fa639e04ce88937423b8570ae4f3a8fd4ff8fe70
3
+ metadata.gz: d27b230b05c9271e1d519b9b946b829da7041d32b1a99a9ae7c29c86326dedd6
4
+ data.tar.gz: 34798fa630422bfa320deb756c445142d67bf17dcd120b246d3b5ab7290246ba
5
5
  SHA512:
6
- metadata.gz: e3c0630c4472b5d5b8f2a479a7b201411a817069221026778f86581146f4ef797dd586d2a0b299df4281394edf7878f1536ceda0d59f766a737255960a07d450
7
- data.tar.gz: df59e4c7c9750e75f976d47ee6d8d632c795702863cdb332627497f88026efa70a2eac2b01520c615a8faf042f677e547d3fec8ae09dc470d50383e1e5ed5548
6
+ metadata.gz: 1950a09e8be9fc31324f7b94733de31c7eedb99f3867addca8ad9dcc4ce0ac9ad240bbccccf26bcc412b7138fc5e30dca14807452962d1768df3aa8b68ece95c
7
+ data.tar.gz: 33c5485fb1fc5e5e7fe135bff40a06ca2d24a61412cce787c2f54af089aa90976fd12ba5e4bda8db2a823e692d5c53ff43508fc8e3c825c21fa2e76d7447ac6e
data/CHANGELOG.md CHANGED
@@ -1,12 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.7
4
+
5
+ #### Bug fixes
6
+
7
+ * Fix a bug on `usable_tag_names` method, so it now properly and expectedly
8
+ includes conditional tag names as well, as before.
9
+
10
+ ## 3.0.6
11
+
12
+ #### Bug fixes
13
+
14
+ * Fix bug on `LMDocstache::Docstache#unusable_tags` method, where `nil` could be
15
+ passed to `broken_tags.deleted_at` call.
16
+
17
+ ## 3.0.5
18
+
19
+ #### Bug fixes and improvements
20
+
21
+ * Improve the way broken tags are detected, making the algorithm wider in terms
22
+ detecting broken tags, specially if the broken tag is the opening part of
23
+ conditional tag blocks (which was being detected before these improvements).
24
+ * Improve the way the paragraphs with "unusable" tags are traversed and have
25
+ their same-style texts merged (hence the "unusable" tags becoming usable). So,
26
+ from now, `w:hyperlink` elements, for instance, are properly processed as
27
+ well.
28
+
29
+ ## 3.0.4
30
+ * Allow replacement `data` argument to be an `Array`. This feature allow to replace blocks
31
+ in a sequentially order following the sequence of matching blocks order.
32
+
33
+ ## 3.0.3
34
+
35
+ ### Bugfix
36
+
37
+ * Hide custom tags arguments was pushing blocks tags to end of paragraph. There are cases this approach
38
+ doesn't work. I changed to be an ordered replacement when we match hide tags.
39
+ * Avoid to merge Tab tags on fix errors methods. This was causing unexpected document changes.
40
+
3
41
  ## 3.0.2
4
42
 
5
43
  ### Bugfix
6
44
 
7
45
  * Fix replacing tags related to hidden custom tags regexp formats. E.g. tab characters.
8
46
 
9
-
10
47
  ## 3.0.1
11
48
 
12
49
  ### Bugfix
data/lib/lm_docstache.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require 'nokogiri'
2
2
  require 'zip'
3
3
  require "lm_docstache/version"
4
+ require "lm_docstache/parser"
4
5
  require "lm_docstache/document"
5
6
  require 'lm_docstache/hide_custom_tags'
6
- require "lm_docstache/parser"
7
7
  require "lm_docstache/condition"
8
8
  require "lm_docstache/conditional_block"
9
9
  require "lm_docstache/renderer"
@@ -1,7 +1,10 @@
1
1
  module LMDocstache
2
2
  class Document
3
- TAGS_REGEXP = /{{.+?}}/
3
+ WHOLE_BLOCK_START_REGEX = /^#{Parser::BLOCK_START_PATTERN}$/
4
+ GENERAL_TAG_REGEX = /\{\{[\/#^]?(.+?)(?:(\s((?:==|~=))\s?.+?))?\}\}/
4
5
  ROLES_REGEXP = /({{(sig|sigfirm|date|check|text|initial)\|(req|noreq)\|(.+?)}})/
6
+ BLOCK_CHILDREN_ELEMENTS = 'w|r,w|hyperlink,w|ins,w|del'
7
+ RUN_LIKE_ELEMENTS = 'w|r,w|ins'
5
8
 
6
9
  def initialize(*paths)
7
10
  raise ArgumentError if paths.empty?
@@ -34,38 +37,50 @@ module LMDocstache
34
37
 
35
38
  def tags
36
39
  @documents.values.flat_map do |document|
37
- document.text.strip.scan(TAGS_REGEXP)
40
+ document_text = document.text
41
+ extract_tag_names(document_text) + extract_tag_names(document_text, true)
38
42
  end
39
43
  end
40
44
 
41
45
  def usable_tags
42
46
  @documents.values.reduce([]) do |tags, document|
43
47
  document.css('w|t').reduce(tags) do |document_tags, text_node|
44
- document_tags.push(*text_node.text.scan(TAGS_REGEXP))
48
+ text = text_node.text
49
+ document_tags.push(*extract_tag_names(text))
50
+ document_tags.push(*extract_tag_names(text, true))
45
51
  end
46
52
  end
47
53
  end
48
54
 
49
55
  def usable_tag_names
50
- usable_tags.reject { |tag| tag =~ ROLES_REGEXP }.map do |tag|
51
- tag.scan(/\{\{[\/#^]?(.+?)(?:(\s((?:==|~=))\s?.+?))?\}\}/)
52
- $1
56
+ usable_tags.reduce([]) do |memo, tag|
57
+ next memo if !tag.is_a?(Regexp) && tag =~ ROLES_REGEXP
58
+
59
+ tag = unescape_escaped_start_block(tag.source) if tag.is_a?(Regexp)
60
+ memo << (tag.scan(GENERAL_TAG_REGEX) && $1)
53
61
  end.compact.uniq
54
62
  end
55
63
 
56
64
  def unusable_tags
57
- unusable_tags = tags
65
+ conditional_start_tags = text_nodes_containing_only_starting_conditionals.map(&:text)
58
66
 
59
- usable_tags.each do |usable_tag|
60
- index = unusable_tags.index(usable_tag)
61
- unusable_tags.delete_at(index) if index
62
- end
67
+ usable_tags.reduce(tags) do |broken_tags, usable_tag|
68
+ next broken_tags unless index = broken_tags.index(usable_tag)
63
69
 
64
- unusable_tags
70
+ broken_tags.delete_at(index) && broken_tags
71
+ end.reject do |broken_tag|
72
+ operator = broken_tag.is_a?(Regexp) ? :=~ : :==
73
+ start_tags_index = conditional_start_tags.find_index do |start_tag|
74
+ broken_tag.send(operator, start_tag)
75
+ end
76
+
77
+ conditional_start_tags.delete_at(start_tags_index) if start_tags_index
78
+ !!start_tags_index
79
+ end
65
80
  end
66
81
 
67
82
  def fix_errors
68
- problem_paragraphs.each { |pg| flatten_paragraph(pg) if pg }
83
+ problem_paragraphs.each { |pg| flatten_text_blocks(pg) if pg }
69
84
  end
70
85
 
71
86
  def errors?
@@ -99,6 +114,34 @@ module LMDocstache
99
114
 
100
115
  private
101
116
 
117
+ def unescape_escaped_start_block(regex_source_string)
118
+ regex_source_string
119
+ .gsub('\\{', '{')
120
+ .gsub('\\#', '#')
121
+ .gsub('\\}', '}')
122
+ .gsub('\\^', '^')
123
+ .gsub('\\ ', ' ')
124
+ end
125
+
126
+ def text_nodes_containing_only_starting_conditionals
127
+ @documents.values.flat_map do |document|
128
+ document.css('w|t').select do |paragraph|
129
+ paragraph.text =~ WHOLE_BLOCK_START_REGEX
130
+ end
131
+ end
132
+ end
133
+
134
+ def extract_tag_names(text, conditional_tag = false)
135
+ if conditional_tag
136
+ text.scan(Parser::BLOCK_MATCHER).map do |match|
137
+ start_block_tag = "{{#{match[0]}#{match[1]} #{match[2]} #{match[3]}}}"
138
+ /#{Regexp.escape(start_block_tag)}/
139
+ end
140
+ else
141
+ text.scan(Parser::VARIABLE_MATCHER).map { |match| "{{#{match[0]}}}" }
142
+ end
143
+ end
144
+
102
145
  def render_documents(data, text = nil, render_options = {})
103
146
  Hash[
104
147
  @documents.map do |(path, document)|
@@ -115,37 +158,48 @@ module LMDocstache
115
158
  def problem_paragraphs
116
159
  unusable_tags.flat_map do |tag|
117
160
  @documents.values.inject([]) do |tags, document|
118
- faulty_paragraphs = document
119
- .css('w|p')
120
- .select { |paragraph| paragraph.text =~ /#{Regexp.escape(tag)}/ }
161
+ faulty_paragraphs = document.css('w|p').select do |paragraph|
162
+ tag_regex = tag.is_a?(Regexp) ? tag : /#{Regexp.escape(tag)}/
163
+ paragraph.text =~ tag_regex
164
+ end
121
165
 
122
166
  tags + faulty_paragraphs
123
167
  end
124
168
  end
125
169
  end
126
170
 
127
- def flatten_paragraph(paragraph)
128
- return if (run_nodes = paragraph.css('w|r')).size < 2
171
+ def flatten_text_blocks(runs_wrapper)
172
+ return if (children = filtered_children(runs_wrapper)).size < 2
173
+
174
+ while node = children.pop
175
+ is_run_node = node.matches?(RUN_LIKE_ELEMENTS)
176
+ previous_node = children.last
129
177
 
130
- while run_node = run_nodes.pop
131
- next if run_nodes.empty?
178
+ if !is_run_node && filtered_children(node, RUN_LIKE_ELEMENTS).any?
179
+ next flatten_text_blocks(node)
180
+ end
181
+ next if !is_run_node || children.empty? || !previous_node.matches?(RUN_LIKE_ELEMENTS)
182
+ next if node.at_css('w|tab') || previous_node.at_css('w|tab')
132
183
 
133
- style_node = run_node.at_css('w|rPr')
184
+ style_node = node.at_css('w|rPr')
134
185
  style_html = style_node ? style_node.inner_html : ''
135
- previous_run_node = run_nodes.last
136
- previous_style_node = previous_run_node.at_css('w|rPr')
186
+ previous_style_node = previous_node.at_css('w|rPr')
137
187
  previous_style_html = previous_style_node ? previous_style_node.inner_html : ''
138
- previous_text_node = previous_run_node.at_css('w|t')
139
- current_text_node = run_node.at_css('w|t')
188
+ previous_text_node = previous_node.at_css('w|t')
189
+ current_text_node = node.at_css('w|t')
140
190
 
141
191
  next if style_html != previous_style_html
142
192
  next if current_text_node.nil? || previous_text_node.nil?
143
193
 
144
- previous_text_node.content = previous_text_node.text + run_node.text
145
- run_node.unlink
194
+ previous_text_node.content = previous_text_node.text + current_text_node.text
195
+ node.unlink
146
196
  end
147
197
  end
148
198
 
199
+ def filtered_children(node, selector = BLOCK_CHILDREN_ELEMENTS)
200
+ Nokogiri::XML::NodeSet.new(node.document, node.children.filter(selector))
201
+ end
202
+
149
203
  def unzip_read(zip, zip_path)
150
204
  file = zip.find_entry(zip_path)
151
205
  contents = ""
@@ -30,15 +30,15 @@ module LMDocstache
30
30
  while run_node = run_nodes.shift
31
31
  next unless run_node.at_css('w|t')
32
32
  next unless run_node.text =~ full_pattern
33
- remainder_run_node = run_node.clone
34
- run_node.unlink
35
- tag_contents = split_tag_content(remainder_run_node.text, full_pattern)
33
+ tag_contents = split_tag_content(run_node.text, full_pattern)
34
+ replacement_nodes = []
36
35
  tag_contents[:content_list].each_with_index do |content, idx|
36
+ remainder_run_node = run_node.clone
37
37
  replace_content(remainder_run_node, content)
38
38
  matched_tag = tag_contents[:matched_tags][idx]
39
- nodes_list = [remainder_run_node]
39
+ replacement_nodes << remainder_run_node
40
40
  if matched_tag
41
- run_node_with_match = remainder_run_node.dup
41
+ run_node_with_match = run_node.clone
42
42
  replace_style(run_node_with_match)
43
43
  matched_content = matched_tag
44
44
  if value
@@ -47,11 +47,11 @@ module LMDocstache
47
47
  value.to_s
48
48
  end
49
49
  replace_content(run_node_with_match, matched_content)
50
- nodes_list << run_node_with_match
50
+ replacement_nodes << run_node_with_match
51
51
  end
52
- paragraph << Nokogiri::XML::NodeSet.new(document, nodes_list)
53
- remainder_run_node = remainder_run_node.clone
54
52
  end
53
+ run_node.add_next_sibling(Nokogiri::XML::NodeSet.new(document, replacement_nodes))
54
+ run_node.unlink
55
55
  end
56
56
  end
57
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
@@ -47,7 +62,8 @@ module LMDocstache
47
62
  # will be the value that will replace the matched string
48
63
  def initialize(document, data, options = {})
49
64
  @document = document
50
- @data = data.transform_keys(&:to_s)
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) }
51
67
  @special_variable_replacements = add_blocks_to_regexp(options.fetch(:special_variable_replacements, {}))
52
68
  @hide_custom_tags = add_blocks_to_regexp(options.fetch(:hide_custom_tags, {}))
53
69
  end
@@ -65,6 +81,7 @@ module LMDocstache
65
81
  hide_custom_tags!
66
82
  find_blocks
67
83
  replace_conditional_blocks_in_document!
84
+ replace_data_sequentially_in_document!
68
85
  replace_variables_in_document!
69
86
  end
70
87
 
@@ -140,8 +157,35 @@ module LMDocstache
140
157
  end
141
158
  end
142
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
+
143
186
  def has_skippable_variable?(text)
144
- return true if hide_custom_tags.find { |(pattern, value)| text =~ pattern }
187
+ return true if hide_custom_tags.find { |(pattern, _)| text =~ pattern }
188
+
145
189
  !!special_variable_replacements.find do |(pattern, value)|
146
190
  text =~ pattern && value == false
147
191
  end
@@ -1,3 +1,3 @@
1
1
  module LMDocstache
2
- VERSION = "3.0.2"
2
+ VERSION = "3.0.7"
3
3
  end
@@ -62,7 +62,7 @@ describe 'integration test', integration: true do
62
62
  it 'fixes nested xml errors breaking tags' do
63
63
  expect { document.fix_errors }.to change {
64
64
  document.send(:problem_paragraphs).size
65
- }.from(6).to(1)
65
+ }.from(7).to(1)
66
66
 
67
67
  expect(document.send(:problem_paragraphs).first.text).to eq(
68
68
  '{{TAG123-\\-//WITH WE👻IRD CHARS}}'
@@ -70,7 +70,7 @@ describe 'integration test', integration: true do
70
70
  end
71
71
 
72
72
  it 'has the expected amount of usable tags' do
73
- expect(document.usable_tags.count).to eq(43)
73
+ expect(document.usable_tags.count).to eq(28)
74
74
  end
75
75
 
76
76
  it 'has the expected amount of usable roles tags' do
@@ -79,7 +79,7 @@ describe 'integration test', integration: true do
79
79
  end
80
80
 
81
81
  it 'has the expected amount of unique tag names' do
82
- expect(document.usable_tag_names.count).to eq(19)
82
+ expect(document.usable_tag_names.count).to eq(20)
83
83
  end
84
84
 
85
85
  it 'renders file using data' do
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: 3.0.2
4
+ version: 3.0.7
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-04-08 00:00:00.000000000 Z
15
+ date: 2021-06-14 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: nokogiri