docstache 0.0.3 → 0.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
  SHA1:
3
- metadata.gz: e82b56fd6b5c8941104ade8ba8c8d683a1e01389
4
- data.tar.gz: 60abfa4c732098cbf233088d00d84bed12d15546
3
+ metadata.gz: 498cab83d9bdadd7f60741682832abff60440b8b
4
+ data.tar.gz: 7ae7842084c4e2f9d32e06df3d4892664975dd68
5
5
  SHA512:
6
- metadata.gz: 4293f3d05fab0da40b7d0bcffbd5df4cd583ab00e61b158b7e5ecab247566cb6f99cb7623b1528df2c0bebb19e1882ee0d10a811693d061dc1738d21c6bfaeaf
7
- data.tar.gz: c67664422e5d74242af736abc33909ba0626ae0ca52c002213d855c2c58a10c1b92ad7a66da21e227a84a779e092f512bd2eba69a17929e3da7cdde0984a2f22
6
+ metadata.gz: 33dcab26cfadbc1c6af24fb888faa084c7a31e00abdc2975b1ddab8a4946d4c684063d764b41e14efb0f2973b70e2d8c5229887fffef6b3288c602c8669be27e
7
+ data.tar.gz: c8f53d99e0b6c00d23533f046826ec9fd042b0678e5c09d8a1df5ea618dd0d0eaf90989510039dff02db27c8cd0fd4eecc94037d4ce4bbc55cdd79098bd515bd
data/changelog.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## 0.0.4
4
+
5
+ * Rewrite of the Renderer class
6
+ * Conditional Blocks now work
7
+ * Loops now work
8
+
9
+ ## 0.0.3
10
+
11
+ * Added ability to have nested tags like `{{purchase_order.number}}` through the
12
+ use of the `Docstache::DataScope` class
13
+
14
+ ## 0.0.2
15
+
16
+ * Fixed a bug that made appended documents not get appended to the *end* of a
17
+ document
18
+ * Fixed a bug that made merged documents not open in Word. This was caused due
19
+ to the `section properties` tag in the xml getting used more than once, since
20
+ each document had one. Now only the first `sectPr` tag is kept.
21
+
22
+ ## 0.0.1
23
+
24
+ * Initial release. Still not working:
25
+ * Nested tags `{{foo.bar}}`
26
+ * Blocks (loops and conditiontals) `{{#foo}} ... {{/foo}}`
@@ -0,0 +1,56 @@
1
+ module Docstache
2
+ class Block
3
+ attr_reader :name, :opening_element, :content_elements, :closing_element, :inverted
4
+ def initialize(name:, data:, opening_element:, content_elements:, closing_element:, inverted:)
5
+ @name = name
6
+ @data = data
7
+ @opening_element = opening_element
8
+ @content_elements = content_elements
9
+ @closing_element = closing_element
10
+ @inverted = inverted
11
+ end
12
+
13
+ def type
14
+ @type ||= if @inverted
15
+ :conditional
16
+ else
17
+ if @data.get(@name).is_a? Array
18
+ :loop
19
+ else
20
+ :conditional
21
+ end
22
+ end
23
+ end
24
+
25
+ def loop?
26
+ type == :loop
27
+ end
28
+
29
+ def conditional?
30
+ type == :conditional
31
+ end
32
+
33
+ def self.find_all(name:, data:, elements:, inverted:)
34
+ if elements.text.match(/\{\{#{inverted ? '\^' : '\#'}#{name}\}\}.+\{\{\/#{name}\}\}/m)
35
+ if elements.any? { |e| e.text.match(/\{\{#{inverted ? '\^' : '\#'}#{name}\}\}.+\{\{\/#{name}\}\}/m) }
36
+ matches = elements.select { |e| e.text.match(/\{\{#{inverted ? '\^' : '\#'}#{name}\}\}.+\{\{\/#{name}\}\}/m) }
37
+ finds = matches.map { |match| find_all(name: name, data: data, elements: match.elements, inverted: inverted) }.flatten
38
+ return finds
39
+ else
40
+ opening = elements.select { |e| e.text.match(/\{\{#{inverted ? '\^' : '\#'}#{name}\}\}/) }.first
41
+ content = []
42
+ next_sibling = opening.next
43
+ while !next_sibling.text.match(/\{\{\/#{name}\}\}/)
44
+ content << next_sibling
45
+ next_sibling = next_sibling.next
46
+ end
47
+ closing = next_sibling
48
+ return Block.new(name: name, data: data, opening_element: opening, content_elements: content, closing_element: closing, inverted: inverted)
49
+ end
50
+ else
51
+ raise "Block not found in given elements"
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -14,7 +14,7 @@ module Docstache
14
14
  end
15
15
 
16
16
  def tags
17
- @document.text.gsub(/\s+/, '').scan(/\{\{[\w\.\/\#]+\}\}/)
17
+ @document.text.gsub(/\s+/, '').scan(/\{\{[\w\.\/\#\^]+\}\}/)
18
18
  end
19
19
 
20
20
  def usable_tags
@@ -1,151 +1,80 @@
1
1
  module Docstache
2
2
  class Renderer
3
+ BLOCK_REGEX = /\{\{([\#\^])([\w\.]+)\}\}.+?\{\{\/\g<2>\}\}/m
4
+
3
5
  def initialize(xml, data)
4
6
  @content = xml
5
7
  @data = DataScope.new(data)
6
8
  end
7
9
 
8
10
  def render
9
- process_content
11
+ find_and_expand_blocks
12
+ replace_tags(@content, @data)
10
13
  return @content
11
14
  end
12
15
 
13
16
  private
14
17
 
15
- def process_content
16
- parse_content(@content.elements)
17
-
18
- content_tr = @content.xpath('//w:tr')
19
-
20
- cleanup_loop(content_tr)
21
- end
22
-
23
- def extract_end_row(nd, key)
24
- if !nd.nil?
25
- case nd.text.to_s
26
- when /\{\{\/#{key.to_s}\}\}/
27
- puts "Found End Row for #{key.to_s}"
28
- return nd
29
- else
30
- return extract_end_row(nd.next, key)
31
- end
32
- else
33
- return nil
34
- end
35
- end
36
-
37
- def expand_loop(nd, end_nd, key, element)
38
- out = []
39
- case nd.text.to_s
40
- when /\{\{\##{key.to_s}\}\}/
41
- out = expand_loop(nd.next, end_nd, key, element)
42
- when end_nd.text.to_s
43
- out = []
44
- when /\{\{\#([a-zA-Z0-9_\.]+)\}\}/
45
- new_key = $1
46
- out += process_loop(nd, new_key, element)
47
- else
48
- new_node = nd.dup
49
- puts "Adding Row #{nd.text} to list"
50
- parse_content(new_node.elements, element)
51
- out << new_node
52
- puts "Next Node is: #{nd.next.text.to_s}"
53
- out += expand_loop(nd.next, end_nd, key, element)
18
+ def find_and_expand_blocks
19
+ blocks = @content.text.scan(BLOCK_REGEX)
20
+ found_blocks = blocks.uniq.map { |block|
21
+ inverted = block[0] == "^"
22
+ Block.find_all(name: block[1], elements: @content.elements, data: @data, inverted: inverted)
23
+ }.flatten
24
+ found_blocks.each do |block|
25
+ expand_and_replace_block(block)
54
26
  end
55
- return out
56
27
  end
57
28
 
58
-
59
- def remove_loop(nd, key)
60
- if nd
61
- case nd.text.to_s
62
- when /\{\{\/#{key.upcase.to_s}\}\}/
63
- nd.unlink
29
+ def expand_and_replace_block(block)
30
+ case block.type
31
+ when :conditional
32
+ case condition = @data.get(block.name)
33
+ when Array
34
+ condition = !condition.empty?
64
35
  else
65
- remove_loop(nd.next, key)
66
- nd.unlink
36
+ condition = !!condition
67
37
  end
68
- end
69
- end
70
-
71
-
72
- def process_loop(nd, key, data)
73
- out = []
74
- # puts "Found Loop #{key.to_s}"
75
- # end_row = extract_end_row(nd, key)
76
- #
77
- # if !data.has_key?(key)
78
- # nil # Error in the data model
79
- # return []
80
- # elsif data[key].empty?
81
- # remove_loop(nd, key) # No data to put in
82
- # return []
83
- # else # Actual loop to process
84
- # data_set = data[key]
85
- # puts "Expanding Rows for loop #{key.to_s}"
86
- # puts "Data count is #{data_set.count}"
87
- # puts "Data is #{data_set}"
88
- #
89
- # data_set.each do |element|
90
- # out += expand_loop(nd, end_row, key, element)
91
- # end
92
- # return out
93
- # end
94
- end
95
-
96
- def parse_content(elements, data=@data)
97
- elements.each do |nd|
98
- case nd.name
99
- when "tr"
100
- case nd.text.to_s
101
- when /\{\{\#([a-zA-Z0-9_\.]+)\}\}/
102
- key = $1
103
- # Get elements to add
104
- elements = process_loop(nd, key, data)
105
- # Add elements
106
- elements.reverse.each do |e|
107
- puts "Adding Row to file: #{e.text.to_s}"
108
- nd.add_next_sibling(e)
109
- end
110
- else # it's a normal table row
111
- parse_content(nd.elements, data)
38
+ condition = !condition if block.inverted
39
+ unless condition
40
+ block.content_elements.each(&:unlink)
41
+ end
42
+ when :loop
43
+ set = @data.get(block.name)
44
+ content = set.map { |item|
45
+ data = DataScope.new(item, @data)
46
+ elements = block.content_elements.map(&:clone)
47
+ replace_tags(Nokogiri::XML::NodeSet.new(@content, elements), data)
48
+ }
49
+ content.each do |els|
50
+ el = els[0]
51
+ els[1..-1].each do |next_el|
52
+ el.after(next_el)
53
+ el = next_el
112
54
  end
113
- when "t" # It's a leaf that contains data to replace
114
- subst_content(nd, data)
115
- else # it's neither a leaf or a loop so let's process it
116
- parse_content(nd.elements, data)
55
+ block.closing_element.before(els[0])
117
56
  end
57
+ block.content_elements.each(&:unlink)
118
58
  end
59
+ block.opening_element.unlink
60
+ block.closing_element.unlink
119
61
  end
120
62
 
121
- def cleanup_loop(nodeset) # Acts in w/tr only as loops are based on these
122
- nodeset.each do |nd|
123
- case nd.text.to_s
124
- when /\{\{\#([a-zA-Z0-9_\.]+)\}\}/
125
- nd.unlink
126
- when /\{\{\/([a-zA-Z0-9_\.]+)\}\}/
127
- nd.unlink
128
- when /\{\{[a-zA-Z0-9_\.]+\}\}/
129
- nd.unlink
63
+ def replace_tags(elements, data)
64
+ elements.css('w|t').each do |text_el|
65
+ if !(results = text_el.text.scan(/\{\{([\w\.]+)\}\}/).flatten).empty?
66
+ rendered_string = text_el.text
67
+ results.each do |r|
68
+ rendered_string.gsub!(/\{\{#{r}\}\}/, text(data.get(r)))
69
+ end
70
+ text_el.content = rendered_string
130
71
  end
131
72
  end
73
+ return elements
132
74
  end
133
75
 
134
- def subst_content(nd, data)
135
- inner = nd.inner_html
136
- keys = nd.text.scan(/\{\{([a-zA-Z0-9_\.]+)\}\}/).map(&:first)
137
- keys.each do |key|
138
- value = data.get(key)
139
- puts "Substituting {{#{key.to_s}}} with #{value}"
140
- inner.gsub!("{{#{key.to_s}}}", safe(value))
141
- end
142
- if !keys.empty?
143
- nd.inner_html = inner
144
- end
145
- end
146
-
147
- def safe(text)
148
- text.to_s
76
+ def text(obj)
77
+ "#{obj}"
149
78
  end
150
79
 
151
80
  end
@@ -1,3 +1,3 @@
1
1
  module Docstache
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/docstache.rb CHANGED
@@ -2,6 +2,7 @@ require 'nokogiri'
2
2
  require 'zip'
3
3
 
4
4
  require "docstache/data_scope"
5
+ require "docstache/block"
5
6
  require "docstache/renderer"
6
7
  require "docstache/document"
7
8
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docstache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Will Cosgrove
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-05 00:00:00.000000000 Z
11
+ date: 2014-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -65,8 +65,10 @@ files:
65
65
  - LICENSE.txt
66
66
  - README.md
67
67
  - Rakefile
68
+ - changelog.md
68
69
  - docstache.gemspec
69
70
  - lib/docstache.rb
71
+ - lib/docstache/block.rb
70
72
  - lib/docstache/data_scope.rb
71
73
  - lib/docstache/document.rb
72
74
  - lib/docstache/renderer.rb
@@ -98,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
100
  version: '0'
99
101
  requirements: []
100
102
  rubyforge_project:
101
- rubygems_version: 2.2.2
103
+ rubygems_version: 2.4.4
102
104
  signing_key:
103
105
  specification_version: 4
104
106
  summary: Merges Hash of Data into Word docx template files using mustache syntax
@@ -110,4 +112,3 @@ test_files:
110
112
  - spec/integration_spec.rb
111
113
  - spec/spec_helper.rb
112
114
  - spec/template_processor_spec.rb
113
- has_rdoc: