odf-report 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest ADDED
@@ -0,0 +1,7 @@
1
+ lib/odf-report.rb
2
+ odf-report.gemspec
3
+ Rakefile
4
+ README.textile
5
+ test/test.odt
6
+ test/test.rb
7
+ Manifest
data/README.textile ADDED
@@ -0,0 +1,181 @@
1
+ h1. ODF-REPORT
2
+
3
+ Gem for generating .odt files by making strings, images and tables substitutions in a previously created .odt file.
4
+
5
+ <hr/>
6
+
7
+ h3. INSTALL
8
+
9
+ @gem install sandrods-odf-report --source=http://gems.github.com@
10
+
11
+ <hr/>
12
+
13
+ h3. USAGE
14
+
15
+ h4. Step 1 -- the template
16
+
17
+ First of all, you need to create a .odt file to serve as a template
18
+
19
+ Templates are normal .odt files with placeholders for Substitutions
20
+
21
+ There are now three kinds of substitutions available: *fields*, *tables* and *images*.
22
+
23
+ h4. Fields placeholders
24
+
25
+ It's just an upcase sentence, surrounded by brackets. It will be replaced for wathever value you supply.
26
+
27
+ In the folowing example:
28
+
29
+ <pre>
30
+ report = ODFReport.new("Users/john/my_template.odt") do |r|
31
+
32
+ r.add_field :user_name, @user.name
33
+ r.add_field :address, "My new address"
34
+
35
+ end
36
+ </pre>
37
+
38
+ All occurences of @[USER_NAME]@ found in the file will be replaced by the value of @@user.name@ whereas all @[ADDRESS]@ 'es will contains @My new address@
39
+
40
+ It's as simple as that.
41
+
42
+ h4. Table placeholders
43
+
44
+ To use table placeholders, you should create a Table in your document and give it a name. In OpenOffice, it's just a matter of right-clicking the table you just created, choose _Table Properties..._ and type a name in the Name field.
45
+
46
+ If the table has two rows, the first one will be treated as a *header* and left untouched. Otherwise you should use a table with one row only.
47
+
48
+ As with Field placeholders, just insert a @[FIELD_NAME]@ in each cell and let the magic takes place.
49
+
50
+ Taking the folowing example:
51
+
52
+ <pre>
53
+ report = ODFReport.new("Users/john/my_template.odt") do |r|
54
+
55
+ r.add_field "USER_NAME", @user.nome
56
+ r.add_field "ADDRESS", @user.address
57
+
58
+ r.add_table("TABLE_1", @list_of_itens) do |row, item|
59
+ row["ITEM_ID"] = item.id
60
+ row["DESCRIPTION"] = "==> #{item.description}"
61
+ end
62
+
63
+ end
64
+ </pre>
65
+
66
+ and considering you have a table like this in your template
67
+
68
+ <pre>
69
+ ---------------------------------
70
+ | [ITEM_ID] | [DESCRIPTION] |
71
+ ---------------------------------
72
+
73
+ * this is my lame attempt to draw a table.
74
+ you don't suppose to type this.
75
+ you have to use an actual table.
76
+ i don't know... just thought I'd mention it ;-)
77
+ </pre>
78
+
79
+ and a collection @list_of_itens, it will be created one row for each item in the collection, and the replacement will take place accordingly.
80
+
81
+ Any format applied to the fields in the template will be preserved.
82
+
83
+ h4. Images
84
+
85
+ You must put a mock image in your odt template and give it a name. That name will be used to replace the mock image for the actual image.
86
+ You can also assign any properties you want to the mock image and they will be kept once the image is replaced.
87
+
88
+ An image replace would look like this:
89
+
90
+ <pre>
91
+ report = ODFReport.new("Users/john/my_template.odt") do |r|
92
+
93
+ r.add_image :graphics1, "/path/to/the/image.jpg"
94
+
95
+ end
96
+ </pre>
97
+
98
+ And that's it.
99
+
100
+ <hr/><br/>
101
+
102
+ h4. Step 2 -- generating the document
103
+
104
+ It's fairly simple to generate the document. You can use this inside a Rails application or in a standalone script.
105
+
106
+ h4. Generating a document in a Rails application
107
+
108
+ In a controller, you can have a code like this:
109
+
110
+ <pre>
111
+ def print
112
+
113
+ @ticket = Ticket.find(params[:id])
114
+
115
+ report = ODFReport.new("#{RAILS_ROOT}/app/reports/ticket.odt") do |r|
116
+
117
+ r.add_field(:id, @ticket.id.to_s)
118
+ r.add_field(:created_by, @ticket.created_by)
119
+ r.add_field(:created_at, @ticket.created_at.strftime("%d/%m/%Y - %H:%M"))
120
+ r.add_field(:type, @ticket.type.name)
121
+ r.add_field(:status, @ticket.status_text)
122
+ r.add_field(:date, Time.now.strftime("%d/%m/%Y - %H:%M"))
123
+ r.add_field(:solution, (@ticket.solution || ''))
124
+
125
+ r.add_table("OPERATORS", @ticket.operators) do | row, op |
126
+ row["OPERATOR_NAME"] = "#{op.name} (#{op.department.short_name})"
127
+ end
128
+
129
+ r.add_table("FIELDS", @ticket.fields) do | row, field |
130
+
131
+ if field.is_a?(String)
132
+ row["FIELD_NAME"] = 'Materials'
133
+ row["FIELD_VALUE"] = field
134
+ else
135
+ row["FIELD_NAME"] = field.name
136
+ row["FIELD_VALUE"] = field.text_value || ''
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+
143
+ report_file_name = report.generate
144
+
145
+ send_file(report_file_name)
146
+
147
+ end
148
+ </pre>
149
+
150
+ The @generate@ method will, er... generate the document in a temp dir and returns the full path of the generated file, so you can send it back to the user.
151
+
152
+ _That's all I have to say about that._
153
+
154
+ h4. Generating a document in a standalone script
155
+
156
+ It's just the same as in a Rails app, but you can inform the path where the file will be generated instead of using a temp dir.
157
+
158
+ <pre>
159
+ report = ODFReport.new("ticket.odt") do |r|
160
+
161
+ ... populates the report ...
162
+
163
+ end
164
+
165
+ report.generate("./documents/")
166
+ </pre>
167
+
168
+ <hr/>
169
+
170
+ h3. REQUIREMENTS
171
+
172
+ *rubyzip*: for manipulating the contents of the odt file, since it's actually a zip file.
173
+
174
+
175
+ <hr/>
176
+
177
+ h3. THE FUTURE
178
+
179
+ Well, this is my first attempt. This gem was extracted from an actual project we developed internally, to fulfill our specific needs.
180
+
181
+ That said, I would really appreciate any input you can come up with. Critics, suggestions, bug reports are welcome and will be thoroughly examined.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('odf-report', '0.1.1') do |p|
6
+ p.description = "Generates ODF files, given a template (.odt) and data, replacing tags"
7
+ p.url = ""
8
+ p.author = "Sandro Duarte"
9
+ p.email = "sandrods@gmail.com"
10
+ p.ignore_pattern = ["tmp/*", "script/*"]
11
+ p.development_dependencies = []
12
+ p.runtime_dependencies = ['rubyzip >= 0.9.1']
13
+ end
14
+
data/lib/odf-report.rb ADDED
@@ -0,0 +1,218 @@
1
+ require 'rubygems'
2
+ require 'zip/zipfilesystem'
3
+ require 'fileutils'
4
+
5
+ class ODFReport
6
+
7
+ def initialize(template_name, &block)
8
+ @template = template_name
9
+ @data={:values=>{}, :tables=>{}, :images => {} }
10
+
11
+ @tmp_dir = Dir.tmpdir + "/" + random_filename(:prefix=>'odt_')
12
+ Dir.mkdir(@tmp_dir) unless File.exists? @tmp_dir
13
+
14
+ yield self
15
+ end
16
+
17
+ def add_field(field_tag, value)
18
+ @data[:values][field_tag] = value
19
+ end
20
+
21
+ def add_table(table_tag, collection, &block)
22
+
23
+ @data[:tables][table_tag] = []
24
+
25
+ collection.each do |item|
26
+ row = {}
27
+ yield(row, item)
28
+ @data[:tables][table_tag] << row
29
+ end
30
+
31
+ end
32
+
33
+ def add_image(name, path)
34
+ @data[:images][name] = path
35
+ end
36
+
37
+ def generate(dest = nil)
38
+
39
+ if dest
40
+
41
+ FileUtils.cp(@template, dest)
42
+ new_file = dest
43
+
44
+ else
45
+
46
+ FileUtils.cp(@template, @tmp_dir)
47
+ new_file = "#{@tmp_dir}/#{File.basename(@template)}"
48
+
49
+ end
50
+
51
+ %w(content.xml styles.xml).each do |content_file|
52
+
53
+ update_file_from_zip(new_file, content_file) do |txt|
54
+
55
+ replace_fields!(txt)
56
+ replace_tables!(txt)
57
+ replace_image_refs!(txt)
58
+ end
59
+
60
+ end
61
+
62
+ unless @data[:images].empty?
63
+ image_dir_name = "Pictures"
64
+ dir = File.join("#{@tmp_dir}", image_dir_name)
65
+ add_image_files_to_dir(dir)
66
+ add_dir_to_zip(new_file, dir, image_dir_name)
67
+ end
68
+
69
+ new_file
70
+
71
+ end
72
+
73
+ private
74
+
75
+ def add_image_files_to_dir(dir)
76
+ FileUtils.mkdir(dir)
77
+ @data[:images].each_pair do |name, path|
78
+ FileUtils.cp(path, File.join(dir, File.basename(path)))
79
+ end
80
+ end
81
+
82
+ def add_dir_to_zip(zip_file, dir, entry)
83
+ Zip::ZipFile.open(zip_file, true) do |z|
84
+ Dir["#{dir}/**/*"].each { |f| z.add("#{entry}/#{File.basename(f)}", f) }
85
+ end
86
+ end
87
+
88
+ def update_file_from_zip(zip_file, content_file, &block)
89
+
90
+ Zip::ZipFile.open(zip_file) do |z|
91
+ cont = "#{@tmp_dir}/#{content_file}"
92
+
93
+ z.extract(content_file, cont)
94
+
95
+ txt = ''
96
+
97
+ File.open(cont, "r") do |f|
98
+ txt = f.read
99
+ end
100
+
101
+ yield(txt)
102
+
103
+ File.open(cont, "w") do |f|
104
+ f.write(txt)
105
+ end
106
+
107
+ z.replace(content_file, cont)
108
+ end
109
+
110
+ end
111
+
112
+
113
+ def replace_fields!(content)
114
+ hash_gsub!(content, @data[:values])
115
+ end
116
+
117
+ def replace_image_refs!(content)
118
+ @data[:images].each_pair do |image_name, path|
119
+ #Set the new image path
120
+ new_path = File.join("Pictures", File.basename(path))
121
+ #Search for the image
122
+ image_rgx = Regexp.new("draw:name=\"#{image_name}\".*?><draw:image.*?xlink:href=\"([^\s]*)\" .*?/></draw:frame>")
123
+ content_match = content.match(image_rgx)
124
+ if content_match
125
+ replace_path = content_match[1]
126
+ content.gsub!(content_match[0], content_match[0].gsub(replace_path, new_path))
127
+ end
128
+ end
129
+ end
130
+
131
+ def replace_tables!(content)
132
+
133
+ @data[:tables].each do |table_name, records|
134
+
135
+ # search for the table inside the content
136
+ table_rgx = Regexp.new("(<table:table table:name=\"#{table_name}.*?>.*?<\/table:table>)", "m")
137
+ table_match = content.match(table_rgx)
138
+
139
+ if table_match
140
+ table = table_match[0]
141
+
142
+ # extract the table from the content
143
+ content.gsub!(table, "[TABLE_#{table_name}]")
144
+
145
+ # search for the table:row's
146
+ row_rgx = Regexp.new("(<table:table-row.*?<\/table:table-row>)", "m")
147
+
148
+ # use scan (instead of match) as the table can have more than one table-row (header and data)
149
+ # and scan returns all matches
150
+ row_match = table.scan(row_rgx)
151
+
152
+ unless row_match.empty?
153
+
154
+ # If there more than one line in the table, takes the second entry (row_match[1])
155
+ # since the first one represents the column header.
156
+ # If there just one line, takes the first line. Besides, since the entry is an Array itself,
157
+ # takes the entry's first element ( entry[0] )
158
+ model_row = (row_match[1] || row_match[0])[0]
159
+
160
+ # extract the row from the table
161
+ table.gsub!(model_row, "[ROW_#{table_name}]")
162
+
163
+ new_rows = ""
164
+
165
+ # for each record
166
+ records.each do |_values|
167
+
168
+ # generates one new row (table-row), based in the model extracted
169
+ # from the original table
170
+ tmp_row = model_row.dup
171
+
172
+ # replace values in the model_row and stores in new_rows
173
+ hash_gsub!(tmp_row, _values)
174
+
175
+ new_rows << tmp_row
176
+ end
177
+
178
+ # replace back the lines into the table
179
+ table.gsub!("[ROW_#{table_name}]", new_rows)
180
+
181
+ end # unless row_match.empty?
182
+
183
+ # replace back the table into content
184
+ content.gsub!("[TABLE_#{table_name}]", table)
185
+
186
+ end # if table match
187
+
188
+ end # tables each
189
+
190
+ end # replace_tables
191
+
192
+ def hash_gsub!(_text, hash_of_values)
193
+ hash_of_values.each do |key, val|
194
+ _text.gsub!("[#{key.to_s.upcase}]", html_escape(val)) unless val.nil?
195
+ end
196
+ end
197
+
198
+ def random_filename(opts={})
199
+ opts = {:chars => ('0'..'9').to_a + ('A'..'F').to_a + ('a'..'f').to_a,
200
+ :length => 24, :prefix => '', :suffix => '',
201
+ :verify => true, :attempts => 10}.merge(opts)
202
+ opts[:attempts].times do
203
+ filename = ''
204
+ opts[:length].times { filename << opts[:chars][rand(opts[:chars].size)] }
205
+ filename = opts[:prefix] + filename + opts[:suffix]
206
+ return filename unless opts[:verify] && File.exists?(filename)
207
+ end
208
+ nil
209
+ end
210
+
211
+ HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
212
+
213
+ def html_escape(s)
214
+ return "" unless s
215
+ s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
216
+ end
217
+
218
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{odf-report}
5
+ s.version = "0.1.3"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Sandro Duarte"]
9
+ s.date = %q{2009-07-28}
10
+ s.description = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
11
+ s.email = %q{sandrods@gmail.com}
12
+ s.extra_rdoc_files = ["lib/odf-report.rb", "README.textile"]
13
+ s.files = ["lib/odf-report.rb", "odf-report.gemspec", "Rakefile", "README.textile", "test/test.odt", "test/test.rb", "Manifest"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Odf-report", "--main", "README.textile"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{odf-report}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<rubyzip>, [">= 0", "= 0.9.1"])
28
+ else
29
+ s.add_dependency(%q<rubyzip>, [">= 0", "= 0.9.1"])
30
+ end
31
+ else
32
+ s.add_dependency(%q<rubyzip>, [">= 0", "= 0.9.1"])
33
+ end
34
+ end
data/test/test.odt ADDED
Binary file
data/test/test.rb ADDED
@@ -0,0 +1,39 @@
1
+ require '../lib/odf-report'
2
+
3
+ col1 = []
4
+ col1 << {:name=>"name 01", :id=>"01", :address=>"this is address 01"}
5
+ col1 << {:name=>"name 03", :id=>"03", :address=>"this is address 03"}
6
+ col1 << {:name=>"name 02", :id=>"02", :address=>"this is address 02"}
7
+ col1 << {:name=>"name 04", :id=>"04", :address=>"this is address 04"}
8
+
9
+ col2 = []
10
+ col2 << {:name=>"josh harnet", :id=>"02", :address=>"testing <&> ", :phone=>99025668, :zip=>"90420-002"}
11
+ col2 << {:name=>"sandro", :id=>"45", :address=>"address with &", :phone=>88774451, :zip=>"90490-002"}
12
+ col2 << {:name=>"ellen bicca", :id=>"77", :address=>"<address with escaped html>", :phone=>77025668, :zip=>"94420-002"}
13
+
14
+ report = ODFReport.new("test.odt") do |r|
15
+
16
+ r.add_field("HEADER_FIELD", "This &field was in the HEADER")
17
+
18
+ r.add_field("TAG_01", "New tag")
19
+ r.add_field("TAG_02", "TAG-2 -> New tag")
20
+
21
+ r.add_table("TABLE_01", col1) do |row, item|
22
+ row["FIELD_01"] = item[:id]
23
+ row["FIELD_02"] = item[:name]
24
+ row["FIELD_03"] = item[:address]
25
+ end
26
+
27
+ r.add_table("TABLE_02", col2) do |row, item|
28
+ row["FIELD_04"] = item[:id]
29
+ row["FIELD_05"] = item[:name]
30
+ row["FIELD_06"] = item[:address]
31
+ row["FIELD_07"] = item[:phone]
32
+ row["FIELD_08"] = item[:zip]
33
+ end
34
+
35
+ r.add_image("graphics1", File.join(Dir.pwd, 'piriapolis.jpg'))
36
+
37
+ end
38
+
39
+ report.generate("result.odt")
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: odf-report
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Sandro Duarte
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-28 00:00:00 -03:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rubyzip
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ - - "="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.1
27
+ version:
28
+ description: Generates ODF files, given a template (.odt) and data, replacing tags
29
+ email: sandrods@gmail.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files:
35
+ - lib/odf-report.rb
36
+ - README.textile
37
+ files:
38
+ - lib/odf-report.rb
39
+ - odf-report.gemspec
40
+ - Rakefile
41
+ - README.textile
42
+ - test/test.odt
43
+ - test/test.rb
44
+ - Manifest
45
+ has_rdoc: true
46
+ homepage: ""
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --line-numbers
52
+ - --inline-source
53
+ - --title
54
+ - Odf-report
55
+ - --main
56
+ - README.textile
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "1.2"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project: odf-report
74
+ rubygems_version: 1.3.5
75
+ signing_key:
76
+ specification_version: 2
77
+ summary: Generates ODF files, given a template (.odt) and data, replacing tags
78
+ test_files: []
79
+