docx_templater 0.0.7 → 0.1.1
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.
- 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
|