docstache 0.0.3 → 0.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
  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: