docx_templater 0.0.7 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/docx/argument_combiner.rb +25 -0
- data/lib/docx/document_replacer.rb +33 -0
- data/lib/docx/nodes_to_fix.rb +38 -0
- data/lib/docx/placeholder_observer.rb +79 -0
- data/lib/docx_templater.rb +7 -63
- metadata +6 -2
@@ -0,0 +1,25 @@
|
|
1
|
+
module Docx
|
2
|
+
class ArgumentCombiner
|
3
|
+
attr_reader :attributes
|
4
|
+
def initialize(*args)
|
5
|
+
@attributes = {}
|
6
|
+
args.flatten!
|
7
|
+
# Prefixes the model name or custom prefix. Makes it so we don't having naming clashes when used with records from multiple m
|
8
|
+
args.each do |arg|
|
9
|
+
if arg.is_a?(Hash) && arg.has_key?(:data) && arg.has_key?(:prefix)
|
10
|
+
template_attributes = (arg[:data].respond_to?(:template_attributes) && :template_attributes) || :attributes
|
11
|
+
arg[:data].send(template_attributes).each_key do |key|
|
12
|
+
@attributes["#{arg[:prefix]}_#{key.to_s}".to_sym] = arg[:data].send(template_attributes)[key]
|
13
|
+
end
|
14
|
+
elsif arg.is_a?(Hash)
|
15
|
+
@attributes.merge!(arg)
|
16
|
+
else
|
17
|
+
template_attributes = (arg.respond_to?(:template_attributes) && :template_attributes) || :attributes
|
18
|
+
arg.send(template_attributes).each_key do |key|
|
19
|
+
@attributes["#{arg.class.name.underscore}_#{key.to_s}".to_sym] = arg.send(template_attributes)[key]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'docx/placeholder_observer'
|
3
|
+
|
4
|
+
module Docx
|
5
|
+
class DocumentReplacer
|
6
|
+
|
7
|
+
attr_reader :doc, :observer
|
8
|
+
|
9
|
+
def initialize(str, data_provider)
|
10
|
+
@doc = REXML::Document.new(str)
|
11
|
+
@observer = Docx::PlaceholderObserver.new(data_provider)
|
12
|
+
walk_node(@doc.root)
|
13
|
+
@observer.end_of_document
|
14
|
+
end
|
15
|
+
|
16
|
+
def replaced
|
17
|
+
@doc.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def walk_node(node)
|
23
|
+
if node.is_a?(REXML::Element)
|
24
|
+
node.children.each do |n|
|
25
|
+
walk_node(n)
|
26
|
+
end
|
27
|
+
elsif node.is_a?(REXML::Text)
|
28
|
+
observer.next_node(node)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Docx
|
2
|
+
class NodesToFix
|
3
|
+
attr_accessor :node_list, :current_node, :value
|
4
|
+
def initialize
|
5
|
+
forget
|
6
|
+
end
|
7
|
+
|
8
|
+
def forget
|
9
|
+
@current_node = nil
|
10
|
+
@node_list = []
|
11
|
+
@value = ''
|
12
|
+
end
|
13
|
+
|
14
|
+
def remember(node, index)
|
15
|
+
new_node = current_node.nil? || current_node != node
|
16
|
+
if new_node
|
17
|
+
@current_node = node
|
18
|
+
@node_list << {:node => node, :range => index..index}
|
19
|
+
else
|
20
|
+
@node_list.last[:range] = (node_list.last[:range].min)..index
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def fix
|
25
|
+
@node_list.each do |obj|
|
26
|
+
node = obj[:node]
|
27
|
+
range = obj[:range]
|
28
|
+
new_val = node.value
|
29
|
+
new_val[range] = value.to_s || ''
|
30
|
+
node.value = new_val
|
31
|
+
if new_val =~ /^\s+/ && node.parent
|
32
|
+
node.parent.add_attribute('xml:space', 'preserve')
|
33
|
+
end
|
34
|
+
self.value = nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'docx/nodes_to_fix'
|
3
|
+
module Docx
|
4
|
+
class PlaceholderObserver
|
5
|
+
attr_reader :data_provider
|
6
|
+
def initialize(data_provider)
|
7
|
+
@data_provider = data_provider
|
8
|
+
@buffer = ''
|
9
|
+
@state = :waiting_for_opening
|
10
|
+
@nodes_to_fix = NodesToFix.new
|
11
|
+
@fixes_to_make = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def next_node(node)
|
15
|
+
node.value.split(//).each_with_index do |c,index|
|
16
|
+
next_char(node,index,c)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def end_of_document
|
21
|
+
make_fixes
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_accessor :state, :buffer, :nodes_to_fix
|
27
|
+
|
28
|
+
def next_char(node, index, c)
|
29
|
+
send(state, node, index, c)
|
30
|
+
end
|
31
|
+
|
32
|
+
def waiting_for_opening(node, index, c)
|
33
|
+
if c == '|'
|
34
|
+
add_char_to_buffer(node,index,c)
|
35
|
+
if buffer == '||'
|
36
|
+
self.state = :capturing_placeholder
|
37
|
+
end
|
38
|
+
else
|
39
|
+
truncate_buffer
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def capturing_placeholder(node, index, c)
|
44
|
+
add_char_to_buffer(node,index,c)
|
45
|
+
if buffer[-2..-1] == '||'
|
46
|
+
key = buffer[2..-3]
|
47
|
+
if data_provider.has_key?(key.to_sym)
|
48
|
+
new_value = data_provider[key.to_sym]
|
49
|
+
save_fix_for_later(new_value)
|
50
|
+
end
|
51
|
+
self.state = :waiting_for_opening
|
52
|
+
truncate_buffer
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_char_to_buffer(node, index, c)
|
57
|
+
@nodes_to_fix.remember(node,index)
|
58
|
+
@buffer << c
|
59
|
+
end
|
60
|
+
|
61
|
+
def truncate_buffer
|
62
|
+
@buffer = ''
|
63
|
+
@nodes_to_fix.forget
|
64
|
+
end
|
65
|
+
|
66
|
+
def save_fix_for_later(val)
|
67
|
+
@nodes_to_fix.value = val
|
68
|
+
@fixes_to_make << @nodes_to_fix
|
69
|
+
@nodes_to_fix = NodesToFix.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def make_fixes
|
73
|
+
@fixes_to_make.reverse.each do |nodes_to_fix|
|
74
|
+
nodes_to_fix.fix
|
75
|
+
end
|
76
|
+
@fixes_to_make = []
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/docx_templater.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'zip/zipfilesystem'
|
2
2
|
require 'htmlentities'
|
3
|
+
require 'docx/argument_combiner'
|
4
|
+
require 'docx/document_replacer'
|
3
5
|
|
4
6
|
# Use .docx as reusable templates
|
5
7
|
#
|
@@ -32,53 +34,20 @@ class DocxTemplater
|
|
32
34
|
end
|
33
35
|
|
34
36
|
def generate_tags_for(*args)
|
35
|
-
attributes
|
36
|
-
args.flatten!
|
37
|
-
# Prefixes the model name or custom prefix. Makes it so we don't having naming clashes when used with records from multiple m
|
38
|
-
args.each do |arg|
|
39
|
-
if arg.is_a?(Hash) && arg.has_key?(:data) && arg.has_key?(:prefix)
|
40
|
-
template_attributes = (arg[:data].respond_to?(:template_attributes) && :template_attributes) || :attributes
|
41
|
-
arg[:data].send(template_attributes).each_key do |key|
|
42
|
-
attributes["#{arg[:prefix]}_#{key.to_s}".to_sym] = arg[:data].send(template_attributes)[key]
|
43
|
-
end
|
44
|
-
elsif arg.is_a?(Hash)
|
45
|
-
attributes.merge!(arg)
|
46
|
-
else
|
47
|
-
template_attributes = (arg.respond_to?(:template_attributes) && :template_attributes) || :attributes
|
48
|
-
arg.send(template_attributes).each_key do |key|
|
49
|
-
attributes["#{arg.class.name.underscore}_#{key.to_s}".to_sym] = arg.send(template_attributes)[key]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
attributes
|
37
|
+
Docx::ArgumentCombiner.new(*args).attributes
|
54
38
|
end
|
55
39
|
|
56
|
-
private
|
57
|
-
def all_tags_regex
|
58
|
-
/\|\|\<*.+?\>*\|\|/
|
59
|
-
end
|
60
|
-
|
61
|
-
def malformed_tag_regex
|
62
|
-
/(?<=>)\w{3,}(?=<)/
|
63
|
-
end
|
64
|
-
|
65
|
-
def well_formed_tag_regex
|
66
|
-
/(?<=\|\|)\w{3,}(?=\|\|)/
|
67
|
-
end
|
68
|
-
|
69
|
-
def just_label_regex
|
70
|
-
/(?<=>)(\w{3,})/
|
71
|
-
end
|
72
|
-
|
73
40
|
def entry_requires_replacement?(entry)
|
74
41
|
entry.ftype != :directory && entry.name =~ /document|header|footer/
|
75
42
|
end
|
76
43
|
|
77
44
|
def get_entry_content(entry, data_provider)
|
45
|
+
file_string = entry.get_input_stream.read
|
78
46
|
if entry_requires_replacement?(entry)
|
79
|
-
|
47
|
+
replacer = Docx::DocumentReplacer.new(file_string, data_provider)
|
48
|
+
replacer.replaced
|
80
49
|
else
|
81
|
-
|
50
|
+
file_string
|
82
51
|
end
|
83
52
|
end
|
84
53
|
|
@@ -86,29 +55,4 @@ class DocxTemplater
|
|
86
55
|
output.put_next_entry(entry.name)
|
87
56
|
output.write get_entry_content(entry, data_provider) if entry.ftype != :directory
|
88
57
|
end
|
89
|
-
|
90
|
-
def replace_entry_content(str, data_provider)
|
91
|
-
possible_tags = str.scan(all_tags_regex)
|
92
|
-
# Loops through what looks like are tags. Anything with ||name|| even if they are not in the available tags list
|
93
|
-
possible_tags.each do |tag|
|
94
|
-
#extracts just the tag name
|
95
|
-
tag_name = malformed_tag_regex.match(tag)
|
96
|
-
tag_name ||= well_formed_tag_regex.match(tag)
|
97
|
-
tag_name ||= ''
|
98
|
-
# This will handle instances where someone edits just part of a tag and Word wraps that part in more XML
|
99
|
-
words = tag.scan(just_label_regex).flatten!
|
100
|
-
if words.respond_to?(:size) && words.size > 1
|
101
|
-
#Then the tag was split by word
|
102
|
-
tag_name = words.join('')
|
103
|
-
end
|
104
|
-
tag_name = tag_name.to_s.to_sym
|
105
|
-
# if in the available tag list, replace with the new value
|
106
|
-
if data_provider.has_key?(tag_name)
|
107
|
-
encoder = HTMLEntities.new
|
108
|
-
content = encoder.encode("#{data_provider[tag_name]}")
|
109
|
-
str.gsub!(tag, content)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
str
|
113
|
-
end
|
114
58
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: docx_templater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -52,6 +52,10 @@ extensions: []
|
|
52
52
|
extra_rdoc_files: []
|
53
53
|
files:
|
54
54
|
- lib/docx_templater.rb
|
55
|
+
- lib/docx/argument_combiner.rb
|
56
|
+
- lib/docx/document_replacer.rb
|
57
|
+
- lib/docx/nodes_to_fix.rb
|
58
|
+
- lib/docx/placeholder_observer.rb
|
55
59
|
homepage: http://rubygems.org/gems/docx_templater
|
56
60
|
licenses: []
|
57
61
|
post_install_message:
|
@@ -72,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
76
|
version: '0'
|
73
77
|
requirements: []
|
74
78
|
rubyforge_project:
|
75
|
-
rubygems_version: 1.8.
|
79
|
+
rubygems_version: 1.8.23
|
76
80
|
signing_key:
|
77
81
|
specification_version: 3
|
78
82
|
summary: Uses a .docx as a template and replaces 'tags' within || with other content
|