odf-report 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +7 -0
- data/README.textile +181 -0
- data/Rakefile +14 -0
- data/lib/odf-report.rb +218 -0
- data/odf-report.gemspec +34 -0
- data/test/test.odt +0 -0
- data/test/test.rb +39 -0
- metadata +79 -0
data/Manifest
ADDED
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 = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
212
|
+
|
213
|
+
def html_escape(s)
|
214
|
+
return "" unless s
|
215
|
+
s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
data/odf-report.gemspec
ADDED
@@ -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
|
+
|