odf-report 0.1.3

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/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
+